forked from ddrilling/asb_cloud_front
Merge branch 'dev' into fix/file-download-page-fix
# Conflicts: # src/pages/FileDownload.jsx
This commit is contained in:
commit
bea165d76e
@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "ЕЦП",
|
||||
"name": "Единая Цифровая Платформа",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
63
src/App.tsx
63
src/App.tsx
@ -1,49 +1,62 @@
|
||||
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { memo } from 'react'
|
||||
import { ConfigProvider } from 'antd'
|
||||
import { lazy, memo, Suspense } from 'react'
|
||||
import locale from 'antd/lib/locale/ru_RU'
|
||||
import { ConfigProvider } from 'antd'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { getUserToken, NoAccessComponent } from '@utils'
|
||||
import { UserOutlet } from '@components/outlets'
|
||||
import LayoutPortal from '@components/LayoutPortal'
|
||||
import SuspenseFallback from '@components/SuspenseFallback'
|
||||
import { getUser, NoAccessComponent } from '@utils'
|
||||
import { OpenAPI } from '@api'
|
||||
|
||||
import 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 FileDownload from '@pages/FileDownload'
|
||||
|
||||
import '@styles/App.less'
|
||||
import '@styles/include/antd_theme.less'
|
||||
import '@styles/App.less'
|
||||
|
||||
//OpenAPI.BASE = 'http://localhost:3000'
|
||||
OpenAPI.TOKEN = async () => getUserToken() ?? ''
|
||||
OpenAPI.HEADERS = {'Content-Type': 'application/json'}
|
||||
const Login = lazy(() => import('@pages/public/Login'))
|
||||
const Register = lazy(() => import('@pages/public/Register'))
|
||||
const FileDownload = lazy(() => import('@pages/FileDownload'))
|
||||
|
||||
const AdminPanel = lazy(() => import('@pages/AdminPanel'))
|
||||
const Deposit = lazy(() => import('@pages/Deposit'))
|
||||
const Cluster = lazy(() => import('@pages/Cluster'))
|
||||
const Well = lazy(() => import('@pages/Well'))
|
||||
|
||||
// OpenAPI.BASE = 'http://localhost:3000'
|
||||
// TODO: Удалить взятие из 'token' в следующем релизе, вставлено для совместимости
|
||||
OpenAPI.TOKEN = async () => getUser().token || localStorage.getItem('token') || ''
|
||||
OpenAPI.HEADERS = { 'Content-Type': 'application/json' }
|
||||
|
||||
export const App = memo(() => (
|
||||
<ConfigProvider locale={locale}>
|
||||
<RootPathContext.Provider value={''}>
|
||||
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100vh' }} />}>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={Deposit.getKey()} replace />} />
|
||||
<Route index element={<Navigate to={'deposit'} 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 />} />
|
||||
<Route path={'/login'} element={<Login />} />
|
||||
<Route path={'/register'} element={<Register />} />
|
||||
|
||||
{/* User pages */}
|
||||
<Route path={Deposit.route} element={<Deposit />} />
|
||||
<Route path={Cluster.route} element={<Cluster />} />
|
||||
<Route path={Well.route} element={<Well />} />
|
||||
<Route path={FileDownload.route} element={<FileDownload />} />
|
||||
<Route element={<UserOutlet />}>
|
||||
<Route path={'/file_download/:idWell/:idFile/*'} element={<FileDownload />} />
|
||||
|
||||
<Route element={<LayoutPortal />}>
|
||||
{/* Admin pages */}
|
||||
<Route path={'/admin/*'} element={<AdminPanel />} />
|
||||
|
||||
{/* Client pages */}
|
||||
<Route path={'/deposit/*'} element={<Deposit />} />
|
||||
<Route path={'/cluster/:idCluster'} element={<Cluster />} />
|
||||
<Route path={'/well/:idWell/*'} element={<Well />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</Suspense>
|
||||
</RootPathContext.Provider>
|
||||
</ConfigProvider>
|
||||
))
|
||||
|
@ -2,8 +2,8 @@ import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { Rule } from 'antd/lib/form'
|
||||
import { Form, Input, Modal, FormProps } from 'antd'
|
||||
|
||||
import { useUser } from '@asb/context'
|
||||
import { AuthService, UserDto } from '@api'
|
||||
import { getUserId, getUserLogin } from '@utils'
|
||||
import { passwordRules, createPasswordRules } from '@utils/validationRules'
|
||||
|
||||
import LoaderPortal from './LoaderPortal'
|
||||
@ -31,7 +31,8 @@ export const ChangePassword = memo<ChangePasswordProps>(({ user, visible, onCanc
|
||||
const [showLoader, setShowLoader] = useState<boolean>(false)
|
||||
const [isDisabled, setIsDisabled] = useState(true)
|
||||
|
||||
const userData = useMemo(() => user ?? { id: getUserId(), login: getUserLogin() } as UserDto, [user])
|
||||
const userContext = useUser()
|
||||
const userData = useMemo(() => user ?? userContext, [user])
|
||||
|
||||
const [form] = Form.useForm()
|
||||
|
||||
@ -63,7 +64,7 @@ export const ChangePassword = memo<ChangePasswordProps>(({ user, visible, onCanc
|
||||
{user && <> (<UserView user={user} />)</>}
|
||||
</>
|
||||
)}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={onModalCancel}
|
||||
onOk={() => form.submit()}
|
||||
okText={'Сохранить'}
|
||||
|
@ -106,7 +106,7 @@ export const ColorPicker = memo<ColorPickerProps>(({ value = '#AA33BB', onChange
|
||||
return (
|
||||
<Popover
|
||||
trigger={'click'}
|
||||
onVisibleChange={onClose}
|
||||
onOpenChange={onClose}
|
||||
content={(
|
||||
<div className={'asb-color-picker-content'}>
|
||||
<div className={'asb-color-picker-sliders'}>
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { memo, ReactNode } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Button, Layout, LayoutProps } from 'antd'
|
||||
|
||||
import PageHeader from '@components/PageHeader'
|
||||
|
||||
export type AdminLayoutPortalProps = LayoutProps & {
|
||||
title?: ReactNode
|
||||
}
|
||||
|
||||
export const AdminLayoutPortal = memo<AdminLayoutPortalProps>(({ title, ...props }) => {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Layout.Content>
|
||||
<PageHeader isAdmin title={title} style={{ backgroundColor: '#900' }}>
|
||||
<Button size={'large'}>
|
||||
<Link to={'/'}>Вернуться на сайт</Link>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Layout>
|
||||
<Layout.Content className={'site-layout-background sheet'} {...props}/>
|
||||
</Layout>
|
||||
</Layout.Content>
|
||||
)
|
||||
})
|
||||
|
||||
export default AdminLayoutPortal
|
@ -1,31 +0,0 @@
|
||||
import { Key, memo, ReactNode } from 'react'
|
||||
import { Layout, LayoutProps } from 'antd'
|
||||
|
||||
import PageHeader from '@components/PageHeader'
|
||||
import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
|
||||
export type LayoutPortalProps = LayoutProps & {
|
||||
title?: ReactNode
|
||||
noSheet?: boolean
|
||||
selector?: WellTreeSelectorProps
|
||||
}
|
||||
|
||||
const _LayoutPortal = memo<LayoutPortalProps>(({ title, noSheet, selector, ...props }) => (
|
||||
<Layout.Content>
|
||||
<PageHeader title={title}>
|
||||
<WellTreeSelector {...selector} />
|
||||
</PageHeader>
|
||||
<Layout>
|
||||
{noSheet ? props.children : (
|
||||
<Layout.Content className={'site-layout-background sheet'} {...props}/>
|
||||
)}
|
||||
</Layout>
|
||||
</Layout.Content>
|
||||
))
|
||||
|
||||
export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, {
|
||||
requirements: ['Deposit.get'],
|
||||
})
|
||||
|
||||
export default LayoutPortal
|
@ -1,5 +0,0 @@
|
||||
export * from './AdminLayoutPortal'
|
||||
export * from './LayoutPortal'
|
||||
|
||||
export type { AdminLayoutPortalProps } from './AdminLayoutPortal'
|
||||
export type { LayoutPortalProps } from './LayoutPortal'
|
136
src/components/LayoutPortal.tsx
Normal file
136
src/components/LayoutPortal.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
import { Breadcrumb, Layout, LayoutProps, Menu, SiderProps } from 'antd'
|
||||
import { Key, memo, ReactNode, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems'
|
||||
import { Link, Outlet } from 'react-router-dom'
|
||||
import {
|
||||
ApartmentOutlined,
|
||||
CodeOutlined,
|
||||
HomeOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
import { LayoutPropsContext } from '@asb/context'
|
||||
import { UserMenu, UserMenuProps } from '@components/UserMenu'
|
||||
import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector'
|
||||
import { isURLAvailable, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import SuspenseFallback from './SuspenseFallback'
|
||||
|
||||
import Logo from '@images/Logo'
|
||||
|
||||
import '@styles/layout.less'
|
||||
|
||||
const { Content, Sider } = Layout
|
||||
|
||||
export type LayoutPortalProps = Omit<LayoutProps, 'children'> & {
|
||||
title?: ReactNode
|
||||
sheet?: boolean
|
||||
showSelector?: boolean
|
||||
selectorProps?: WellTreeSelectorProps
|
||||
sider?: boolean | JSX.Element
|
||||
siderProps?: SiderProps & { userMenuProps?: UserMenuProps }
|
||||
isAdmin?: boolean
|
||||
fallback?: JSX.Element
|
||||
breadcrumb?: boolean | JSX.Element
|
||||
topRightBlock?: JSX.Element
|
||||
}
|
||||
|
||||
const defaultProps: LayoutPortalProps = {
|
||||
title: 'Единая цифровая платформа',
|
||||
sider: true,
|
||||
sheet: true,
|
||||
}
|
||||
|
||||
const makeItem = (title: string, key: Key, icon: JSX.Element, label?: ReactNode, onClick?: () => void) => ({ icon, key, title, label: label ?? title, onClick })
|
||||
|
||||
const _LayoutPortal = memo(() => {
|
||||
const [menuCollapsed, setMenuCollapsed] = useState<boolean>(true)
|
||||
const [wellsTreeOpen, setWellsTreeOpen] = useState<boolean>(false)
|
||||
const [userMenuOpen, setUserMenuOpen] = useState<boolean>(false)
|
||||
const [currentWell, setCurrentWell] = useState<string>('')
|
||||
const [props, setProps] = useState<LayoutPortalProps>(defaultProps)
|
||||
|
||||
const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, breadcrumb, topRightBlock, ...other } = useMemo(() => props, [props])
|
||||
|
||||
const setLayoutProps = useCallback((props: LayoutPortalProps) => setProps({ ...defaultProps, ...props}), [])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof showSelector === 'boolean')
|
||||
setWellsTreeOpen(showSelector)
|
||||
}, [showSelector])
|
||||
|
||||
const menuItems = useMemo(() => [
|
||||
!isAdmin && makeItem(currentWell, 'well', <ApartmentOutlined/>, null, () => setWellsTreeOpen((prev) => !prev)),
|
||||
isAdmin && makeItem('Вернуться на сайт', 'go_back', <HomeOutlined />, <Link to={'/'}>Домой</Link>),
|
||||
!isAdmin && isURLAvailable('/admin') && makeItem('Панель администратора', 'admin_panel', <CodeOutlined />, <Link to={'/admin'}>Панель администратора</Link>),
|
||||
makeItem('Профиль', 'profile', <UserOutlined/>, null, () => setUserMenuOpen((prev) => !prev)),
|
||||
].filter(Boolean) as ItemType[], [isAdmin, currentWell])
|
||||
|
||||
return (
|
||||
<Layout className={`page-layout ${isAdmin ? 'page-layout-admin' : ''}`}>
|
||||
{(sider || siderProps) && (
|
||||
<Sider {...siderProps} collapsedWidth={50} collapsed={menuCollapsed} trigger={null} collapsible className={`menu-sider ${siderProps?.className || ''}`}>
|
||||
<div className={'sider-content'}>
|
||||
<button className={'sider-toogle'} onClick={() => setMenuCollapsed((prev) => !prev)}>
|
||||
<Logo onlyIcon={menuCollapsed} />
|
||||
</button>
|
||||
<div className={'scrollable hide-slider'}>
|
||||
{sider}
|
||||
</div>
|
||||
<Menu
|
||||
mode={'inline'}
|
||||
items={menuItems}
|
||||
theme={'dark'}
|
||||
selectable={false}
|
||||
/>
|
||||
<UserMenu
|
||||
open={userMenuOpen}
|
||||
onClose={() => setUserMenuOpen(false)}
|
||||
isAdmin={isAdmin}
|
||||
{...siderProps?.userMenuProps}
|
||||
/>
|
||||
</div>
|
||||
</Sider>
|
||||
)}
|
||||
{!isAdmin && (
|
||||
<WellTreeSelector
|
||||
open={wellsTreeOpen}
|
||||
onClose={() => setWellsTreeOpen(false)}
|
||||
{...selectorProps}
|
||||
onChange={(well) => setCurrentWell(well ?? 'Выберите месторождение')}
|
||||
/>
|
||||
)}
|
||||
<Layout className={'page-content'}>
|
||||
<Content {...other} className={`${sheet ? 'site-layout-background sheet' : ''} ${other.className ?? ''}`}>
|
||||
{(breadcrumb || topRightBlock) && (
|
||||
<div className={'breadcrumb-block'}>
|
||||
{breadcrumb && (
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href={'/'}>
|
||||
<HomeOutlined />
|
||||
</Breadcrumb.Item>
|
||||
{!isAdmin && (
|
||||
<Breadcrumb.Item>
|
||||
<a style={{ userSelect: 'none' }} onClick={() => setWellsTreeOpen((prev) => !prev)}>{currentWell}</a>
|
||||
</Breadcrumb.Item>
|
||||
)}
|
||||
{breadcrumb}
|
||||
</Breadcrumb>
|
||||
)}
|
||||
{topRightBlock}
|
||||
</div>
|
||||
)}
|
||||
<LayoutPropsContext.Provider value={setLayoutProps}>
|
||||
<Suspense fallback={fallback ?? <SuspenseFallback style={{ minHeight: '100%' }} />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</LayoutPropsContext.Provider>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
})
|
||||
|
||||
export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, { requirements: ['Deposit.get'] })
|
||||
|
||||
export default LayoutPortal
|
56
src/components/MenuBreadcrumb.tsx
Normal file
56
src/components/MenuBreadcrumb.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { Breadcrumb, BreadcrumbItemProps } from 'antd'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { join } from 'path'
|
||||
|
||||
import { PrivateWellMenuItem } from '@components/PrivateWellMenu'
|
||||
import { FunctionalValue, useFunctionalValue } from '@utils'
|
||||
|
||||
export const makeBreadcrumbItems = (items: PrivateWellMenuItem[], pathParts: string[], root: string = '/') => {
|
||||
const out = []
|
||||
const parts = [...pathParts]
|
||||
let route = root
|
||||
let arr: PrivateWellMenuItem[] | undefined = items
|
||||
while (arr && parts.length > 0) {
|
||||
const child: PrivateWellMenuItem | undefined = arr.find(elm => elm.route.toLowerCase() === parts[0].toLowerCase())
|
||||
if (!child) break
|
||||
route = join(route, child.route)
|
||||
out.push({ ...child, route })
|
||||
parts.splice(0, 1)
|
||||
arr = child.children
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export type MenuBreadcrumbItemsProps = {
|
||||
menuItems: PrivateWellMenuItem[]
|
||||
pathRoot?: RegExp
|
||||
itemsProps?: FunctionalValue<(item: PrivateWellMenuItem) => BreadcrumbItemProps>
|
||||
itemRender?: (item: PrivateWellMenuItem) => JSX.Element
|
||||
}
|
||||
|
||||
export const MenuBreadcrumbItems = memo<MenuBreadcrumbItemsProps>(({ menuItems, pathRoot = /^\//, itemsProps, itemRender }) => {
|
||||
const location = useLocation()
|
||||
const getItemProps = useFunctionalValue(itemsProps)
|
||||
|
||||
const items = useMemo(() => {
|
||||
const path = location.pathname
|
||||
const rootPart = pathRoot.exec(path)
|
||||
if (!rootPart || rootPart.length <= 0) return []
|
||||
const root = rootPart[0]
|
||||
const parts = path.trim().slice(root.length).split('/')
|
||||
return makeBreadcrumbItems(menuItems, parts, root)
|
||||
}, [location, menuItems, pathRoot])
|
||||
|
||||
return (
|
||||
<>
|
||||
{items.map((item) => (
|
||||
<Breadcrumb.Item key={item.route} {...getItemProps(item)}>
|
||||
{itemRender ? itemRender(item) : (
|
||||
<Link to={item.route}>{item.title}</Link>
|
||||
)}
|
||||
</Breadcrumb.Item>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
})
|
@ -1,30 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
import { Layout } from 'antd'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { BasicProps } from 'antd/lib/layout/layout'
|
||||
|
||||
import { headerHeight } from '@utils'
|
||||
import { UserMenu } from './UserMenu'
|
||||
|
||||
import Logo from '@images/Logo'
|
||||
|
||||
export type PageHeaderProps = BasicProps & {
|
||||
title?: string
|
||||
isAdmin?: boolean
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const PageHeader: React.FC<PageHeaderProps> = memo(({ title = 'Мониторинг', isAdmin, children, ...other }) => (
|
||||
<Layout>
|
||||
<Layout.Header className={'header'} {...other}>
|
||||
<Link to={'/'} style={{ height: headerHeight }}>
|
||||
<Logo />
|
||||
</Link>
|
||||
<h1 className={'title'}>{title}</h1>
|
||||
{children}
|
||||
<UserMenu isAdmin={isAdmin} />
|
||||
</Layout.Header>
|
||||
</Layout>
|
||||
))
|
||||
|
||||
export default PageHeader
|
@ -1,14 +0,0 @@
|
||||
import { memo, ReactElement } from 'react'
|
||||
|
||||
import { isURLAvailable } from '@utils'
|
||||
|
||||
export type PrivateContentProps = {
|
||||
absolutePath: string
|
||||
children?: ReactElement<any, any>
|
||||
}
|
||||
|
||||
export const PrivateContent = memo<PrivateContentProps>(({ absolutePath, children = null }) =>
|
||||
isURLAvailable(absolutePath) ? children : null
|
||||
)
|
||||
|
||||
export default PrivateContent
|
@ -1,19 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
import { Navigate, Route, RouteProps } from 'react-router-dom'
|
||||
|
||||
import { isURLAvailable } from '@utils'
|
||||
|
||||
import { getDefaultRedirectPath } from './PrivateRoutes'
|
||||
|
||||
export type PrivateDefaultRouteProps = RouteProps & {
|
||||
urls: string[]
|
||||
elseRedirect?: string
|
||||
}
|
||||
|
||||
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,75 +0,0 @@
|
||||
import { join } from 'path'
|
||||
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 { useRootPath } from '@asb/context'
|
||||
import { getTabname, hasPermission, PrivateComponent, PrivateProps } from '@utils'
|
||||
|
||||
export type PrivateMenuProps = MenuProps & { root?: string }
|
||||
|
||||
export type PrivateMenuLinkProps = Partial<ItemType> & Omit<LinkProps, 'to'> & RefAttributes<HTMLAnchorElement> & {
|
||||
icon?: ReactNode
|
||||
danger?: boolean
|
||||
title?: ReactNode
|
||||
content?: PrivateComponent<any>
|
||||
path?: string
|
||||
visible?: boolean
|
||||
permissions?: string[]
|
||||
}
|
||||
|
||||
export const PrivateMenuLink = memo<PrivateMenuLinkProps>(({ content, path = '', title, ...other }) => (
|
||||
<Link to={path} {...other}>{title ?? content?.title}</Link>
|
||||
))
|
||||
|
||||
const PrivateMenuMain = memo<PrivateMenuProps>(({ selectable, mode, selectedKeys, root, children, ...other }) => {
|
||||
const rootContext = useRootPath()
|
||||
const rootPath = useMemo(() => root ?? rootContext ?? '', [root, rootContext])
|
||||
|
||||
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,
|
||||
key,
|
||||
label: <PrivateMenuLink {...child.props} path={path} />,
|
||||
}
|
||||
return null
|
||||
})?.filter((v) => v) ?? [], [children, rootPath])
|
||||
|
||||
return (
|
||||
<Menu
|
||||
selectable={selectable ?? true}
|
||||
mode={mode ?? 'horizontal'}
|
||||
selectedKeys={keys}
|
||||
items={items as ItemType[]}
|
||||
{...other}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export const PrivateMenu = Object.assign(PrivateMenuMain, { Link: PrivateMenuLink })
|
||||
|
||||
export default PrivateMenu
|
@ -1,37 +0,0 @@
|
||||
import { join } from 'path'
|
||||
import { Menu, MenuItemProps } from 'antd'
|
||||
import { memo, NamedExoticComponent } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
import { isURLAvailable } from '@utils'
|
||||
|
||||
export type PrivateMenuItemProps = MenuItemProps & {
|
||||
root: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type PrivateMenuItemLinkProps = MenuItemProps & {
|
||||
root?: string
|
||||
path: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export const PrivateMenuItemLink = memo<PrivateMenuItemLinkProps>(({ root = '', path, title, ...other }) => {
|
||||
const location = useLocation()
|
||||
return (
|
||||
<PrivateMenuItem key={path} root={root} path={path} {...other}>
|
||||
<Link to={join(root, path)}>{title}</Link>
|
||||
</PrivateMenuItem>
|
||||
)
|
||||
})
|
||||
|
||||
export const PrivateMenuItem: NamedExoticComponent<PrivateMenuItemProps> & {
|
||||
Link: NamedExoticComponent<PrivateMenuItemLinkProps>
|
||||
} = Object.assign(memo<PrivateMenuItemProps>(({ root, path, ...other }) =>
|
||||
<Menu.Item key={path} hidden={!isURLAvailable(join(root, path))} {...other} />
|
||||
), {
|
||||
Link: PrivateMenuItemLink
|
||||
})
|
||||
|
||||
|
||||
export default PrivateMenuItem
|
@ -1,30 +0,0 @@
|
||||
import { join } from 'path'
|
||||
import { memo, ReactNode } from 'react'
|
||||
import { Navigate, Route, RouteProps } from 'react-router-dom'
|
||||
|
||||
import { getUserId, isURLAvailable } from '@utils'
|
||||
|
||||
export type PrivateRouteProps = RouteProps & {
|
||||
root?: string
|
||||
path: string
|
||||
children?: ReactNode
|
||||
redirect?: ReactNode
|
||||
}
|
||||
|
||||
export const defaultRedirect = (
|
||||
<Navigate to={getUserId() ? '/access_denied' : '/login'} />
|
||||
)
|
||||
|
||||
export const PrivateRoute = memo<PrivateRouteProps>(({ root = '', path, children, redirect = defaultRedirect, ...other }) => {
|
||||
const available = isURLAvailable(join(root, path))
|
||||
|
||||
return (
|
||||
<Route
|
||||
{...other}
|
||||
path={path}
|
||||
element={available ? children : redirect}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export default PrivateRoute
|
@ -1,67 +0,0 @@
|
||||
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,13 +0,0 @@
|
||||
export { PrivateRoute, defaultRedirect } from './PrivateRoute'
|
||||
export { PrivateContent } from './PrivateContent' // TODO: Remove
|
||||
export { PrivateMenuItem, PrivateMenuItemLink } from './PrivateMenuItem' // TODO: Remove
|
||||
export { PrivateDefaultRoute } from './PrivateDefaultRoute'
|
||||
export { PrivateMenu, PrivateMenuLink } from './PrivateMenu'
|
||||
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 { PrivateRoutesProps } from './PrivateRoutes'
|
95
src/components/PrivateWellMenu.tsx
Normal file
95
src/components/PrivateWellMenu.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems'
|
||||
import { memo, ReactNode, useMemo } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { join } from 'path'
|
||||
import { Menu, MenuProps } from 'antd'
|
||||
|
||||
import { hasPermission, Permission } from '@utils'
|
||||
|
||||
export type PrivateWellMenuItem = {
|
||||
title: string
|
||||
route: string
|
||||
permissions: Permission | Permission[]
|
||||
icon?: ReactNode
|
||||
visible?: boolean
|
||||
children?: PrivateWellMenuItem[]
|
||||
}
|
||||
|
||||
const makeItems = (items: PrivateWellMenuItem[], parentRoute: string, pathParser?: (path: string, parent: string) => string): ItemType[] => {
|
||||
return items.map((item) => {
|
||||
if (item.visible === false || !(item.visible === true || hasPermission(item.permissions))) return null
|
||||
|
||||
let route = item.route
|
||||
if (pathParser)
|
||||
route = pathParser(item.route, parentRoute)
|
||||
else if (!item.route.startsWith('/') && parentRoute)
|
||||
route = join(parentRoute, item.route)
|
||||
|
||||
const out: ItemType = {
|
||||
key: route,
|
||||
icon: item.icon,
|
||||
title: item.title,
|
||||
label: <Link to={route}>{item.title}</Link>,
|
||||
}
|
||||
|
||||
if (item.children && item.children.length > 0) {
|
||||
return {
|
||||
...out,
|
||||
children: makeItems(item.children, route, pathParser)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}).filter(Boolean)
|
||||
}
|
||||
|
||||
const makeItemList = (items: PrivateWellMenuItem[], rootPath: string, wellId?: number): ItemType[] => {
|
||||
const parser = (path: string, parent: string) => {
|
||||
if (!path.startsWith('/'))
|
||||
path = join(parent, path)
|
||||
return path.replace(/\{wellId\}/, String(wellId))
|
||||
}
|
||||
|
||||
return makeItems(items, rootPath, parser)
|
||||
}
|
||||
|
||||
export const makeItem = (
|
||||
title: string,
|
||||
route: string,
|
||||
permissions: Permission | Permission[],
|
||||
icon?: ReactNode,
|
||||
children?: PrivateWellMenuItem[],
|
||||
visible?: boolean
|
||||
): PrivateWellMenuItem => ({
|
||||
title,
|
||||
route,
|
||||
icon,
|
||||
permissions,
|
||||
children,
|
||||
visible,
|
||||
})
|
||||
|
||||
export type PrivateWellMenuProps = Omit<MenuProps, 'items'> & {
|
||||
idWell?: number
|
||||
items: PrivateWellMenuItem[]
|
||||
rootPath?: string
|
||||
}
|
||||
|
||||
export const PrivateWellMenu = memo<PrivateWellMenuProps>(({ idWell, items, rootPath = '/', ...other }) => {
|
||||
const location = useLocation()
|
||||
|
||||
const menuItems = useMemo(() => makeItemList(items, rootPath, idWell), [items, rootPath, idWell])
|
||||
|
||||
const tabKeys = useMemo(() => {
|
||||
const out = []
|
||||
const rx = RegExp(/(?<!^)\//g)
|
||||
let input = location.pathname
|
||||
if (!input.endsWith('/')) input += '/'
|
||||
let match: RegExpExecArray | null
|
||||
while((match = rx.exec(input)) !== null)
|
||||
out.push(input.slice(0, match.index))
|
||||
return out
|
||||
}, [location.pathname])
|
||||
|
||||
return <Menu items={menuItems} selectedKeys={tabKeys} {...other} />
|
||||
})
|
0
src/pages/SuspenseFallback.tsx → src/components/SuspenseFallback.tsx
Executable file → Normal file
0
src/pages/SuspenseFallback.tsx → src/components/SuspenseFallback.tsx
Executable file → Normal file
@ -84,7 +84,7 @@ const _TableSettingsChanger = <T extends object>({ title, columns, settings, onC
|
||||
<>
|
||||
<Modal
|
||||
centered
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={onModalCancel}
|
||||
onOk={onModalOk}
|
||||
title={title ?? 'Настройка отображения таблицы'}
|
||||
|
@ -1,56 +1,110 @@
|
||||
import { memo, MouseEventHandler, useCallback, useState } from 'react'
|
||||
import { memo, ReactNode, useCallback, useState } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Button, Dropdown, DropDownProps } from 'antd'
|
||||
import { UserOutlined } from '@ant-design/icons'
|
||||
import { Button, Collapse, Drawer, DrawerProps, Form, FormRule, Input, Popconfirm } from 'antd'
|
||||
import { useForm } from 'antd/lib/form/Form'
|
||||
|
||||
import { getUserLogin, removeUser } from '@utils'
|
||||
import { ChangePassword } from './ChangePassword'
|
||||
import { PrivateMenu } from './Private'
|
||||
import { useUser } from '@asb/context'
|
||||
import { Grid, GridItem } from '@components/Grid'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { isURLAvailable, removeUser } from '@utils'
|
||||
import { AuthService } from '@api'
|
||||
|
||||
import AdminPanel from '@pages/AdminPanel'
|
||||
import '@styles/user_menu.less'
|
||||
|
||||
type UserMenuProps = Omit<DropDownProps, 'overlay'> & { isAdmin?: boolean }
|
||||
export type UserMenuProps = DrawerProps & {
|
||||
isAdmin?: boolean
|
||||
additional?: ReactNode
|
||||
}
|
||||
|
||||
export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
|
||||
type ChangePasswordForm = {
|
||||
'new-password': string
|
||||
}
|
||||
|
||||
const newPasswordRules: FormRule[] = [{ required: true, message: 'Пожалуйста, введите новый пароль!' }]
|
||||
|
||||
const confirmPasswordRules: FormRule[] = [({ getFieldValue }) => ({ validator(_, value: string) {
|
||||
if (value !== getFieldValue('new-password'))
|
||||
return Promise.reject('Пароли не совпадают!')
|
||||
return Promise.resolve()
|
||||
}})]
|
||||
|
||||
export const UserMenu = memo<UserMenuProps>(({ isAdmin, additional, ...other }) => {
|
||||
const [showLoader, setShowLoader] = useState<boolean>(false)
|
||||
|
||||
const [changeLoginForm] = useForm<ChangePasswordForm>()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const user = useUser()
|
||||
|
||||
const onChangePasswordClick: MouseEventHandler = useCallback((e) => {
|
||||
setIsModalVisible(true)
|
||||
e.preventDefault()
|
||||
}, [])
|
||||
const navigateTo = useCallback((to: string) => navigate(to, { state: { from: location.pathname }}), [navigate, location.pathname])
|
||||
|
||||
const onChangePasswordOk = useCallback(() => {
|
||||
setIsModalVisible(false)
|
||||
navigate('/login', { state: { from: location.pathname }})
|
||||
}, [navigate, location])
|
||||
const onChangePasswordOk = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async (values: any) => {
|
||||
await AuthService.changePassword(user.id ?? -1, `${values['new-password']}`)
|
||||
removeUser()
|
||||
navigateTo('/login')
|
||||
},
|
||||
setShowLoader,
|
||||
`Не удалось сменить пароль пользователя ${user.login}`,
|
||||
{ actionName: 'Смена пароля пользователя' },
|
||||
), [navigateTo])
|
||||
|
||||
const logout = useCallback(() => {
|
||||
removeUser()
|
||||
navigateTo('/login')
|
||||
}, [navigateTo])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
<Drawer
|
||||
closable
|
||||
placement={'left'}
|
||||
className={'user-menu'}
|
||||
title={'Профиль пользователя'}
|
||||
{...other}
|
||||
placement={'bottomRight'}
|
||||
overlay={(
|
||||
<PrivateMenu mode={'vertical'} style={{ textAlign: 'right' }}>
|
||||
{isAdmin ? (
|
||||
<PrivateMenu.Link visible key={'/'} path={'/'} title={'Вернуться на сайт'} />
|
||||
) : (
|
||||
<PrivateMenu.Link path={'/admin'} content={AdminPanel} />
|
||||
)}
|
||||
<PrivateMenu.Link visible key={'change_password'} onClick={onChangePasswordClick} title={'Сменить пароль'} />
|
||||
<PrivateMenu.Link visible key={'login'} path={'/login'} onClick={removeUser} title={'Выход'} />
|
||||
</PrivateMenu>
|
||||
)}
|
||||
>
|
||||
<Button icon={<UserOutlined/>}>{getUserLogin()}</Button>
|
||||
</Dropdown>
|
||||
<ChangePassword
|
||||
visible={isModalVisible}
|
||||
onOk={onChangePasswordOk}
|
||||
onCancel={() => setIsModalVisible(false)}
|
||||
/>
|
||||
</>
|
||||
<div className={'profile-links'}>
|
||||
{isAdmin ? (
|
||||
<Button onClick={() => navigateTo('/')}>Вернуться на сайт</Button>
|
||||
) : isURLAvailable('/admin') && (
|
||||
<Button onClick={() => navigateTo('/admin')}>Панель администратора</Button>
|
||||
)}
|
||||
<Button type={'ghost'} onClick={logout}>Выход</Button>
|
||||
</div>
|
||||
<Collapse>
|
||||
<Collapse.Panel header={'Данные'} key={'summary'}>
|
||||
<Grid>
|
||||
<GridItem row={1} col={1}>Логин:</GridItem>
|
||||
<GridItem row={1} col={2}>{user.login}</GridItem>
|
||||
<GridItem row={2} col={1}>Фамилия:</GridItem>
|
||||
<GridItem row={2} col={2}>{user.surname}</GridItem>
|
||||
<GridItem row={3} col={1}>Имя:</GridItem>
|
||||
<GridItem row={3} col={2}>{user.name}</GridItem>
|
||||
<GridItem row={4} col={1}>Отчество:</GridItem>
|
||||
<GridItem row={4} col={2}>{user.patronymic}</GridItem>
|
||||
<GridItem row={5} col={1}>E-mail:</GridItem>
|
||||
<GridItem row={5} col={2}>{user.email}</GridItem>
|
||||
</Grid>
|
||||
</Collapse.Panel>
|
||||
<Collapse.Panel header={'Смена пароля'} key={'change-password'}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<Form name={'change-password'} form={changeLoginForm} autoComplete={'off'} onFinish={onChangePasswordOk}>
|
||||
<Form.Item name={'new-password'} label={'Новый пароль'} rules={newPasswordRules}>
|
||||
<Input.Password placeholder={'Впишите новый пароль'} />
|
||||
</Form.Item>
|
||||
<Form.Item required name={'confirm-password'} rules={confirmPasswordRules} label={'Подтверждение пароля'}>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Popconfirm title={'Вы уверены что хотите сменить пароль?'} onConfirm={changeLoginForm.submit} placement={'topRight'}>
|
||||
<Button type={'primary'}>Сменить</Button>
|
||||
</Popconfirm>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</LoaderPortal>
|
||||
</Collapse.Panel>
|
||||
{additional}
|
||||
</Collapse>
|
||||
</Drawer>
|
||||
)
|
||||
})
|
||||
|
@ -135,7 +135,7 @@ const _D3MonitoringEditor = <DataType extends BaseDataType>({
|
||||
<Modal
|
||||
centered
|
||||
width={800}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
title={'Настройка групп графиков'}
|
||||
onCancel={onCancel}
|
||||
footer={(
|
||||
|
@ -3,8 +3,7 @@ import { ArgsProps } from 'antd/lib/notification'
|
||||
import { Dispatch, ReactNode, SetStateAction } from 'react'
|
||||
|
||||
import { WellView } from '@components/views'
|
||||
import { getUserToken } from '@utils'
|
||||
import { FunctionalValue, getFunctionalValue, isDev } from '@utils'
|
||||
import { FunctionalValue, getFunctionalValue, getUser, isDev } from '@utils'
|
||||
import { ApiError, FileInfoDto, WellDto } from '@api'
|
||||
|
||||
export type NotifyType = 'error' | 'warning' | 'info'
|
||||
@ -97,7 +96,7 @@ export const invokeWebApiWrapperAsync = async (
|
||||
export const download = async (url: string, fileName?: string) => {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getUserToken()}`
|
||||
Authorization: `Bearer ${getUser().token}`
|
||||
},
|
||||
method: 'Get'
|
||||
})
|
||||
@ -125,7 +124,7 @@ export const download = async (url: string, fileName?: string) => {
|
||||
export const upload = async (url: string, formData: FormData) => {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getUserToken()}`
|
||||
Authorization: `Bearer ${getUser().token}`
|
||||
},
|
||||
method: 'Post',
|
||||
body: formData,
|
||||
|
30
src/components/outlets/UserOutlet.tsx
Normal file
30
src/components/outlets/UserOutlet.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
|
||||
import { UserContext } from '@asb/context'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { getUser, setUser as setStorageUser } from '@utils'
|
||||
import { AuthService, UserTokenDto } from '@api'
|
||||
|
||||
export const UserOutlet = memo(() => {
|
||||
const [user, setUser] = useState<UserTokenDto>({})
|
||||
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(async () => {
|
||||
let user = getUser()
|
||||
if (!user.id) {
|
||||
user = await AuthService.refresh()
|
||||
setStorageUser(user)
|
||||
}
|
||||
setUser(user)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={user}>
|
||||
<Outlet />
|
||||
</UserContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
export default UserOutlet
|
1
src/components/outlets/index.ts
Normal file
1
src/components/outlets/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './UserOutlet'
|
@ -39,8 +39,8 @@ export const Poprompt = memo<PopromptProps>(({ formProps, buttonProps, footer, c
|
||||
)}
|
||||
trigger={'click'}
|
||||
{...other}
|
||||
visible={visible}
|
||||
onVisibleChange={(visible) => setVisible(visible)}
|
||||
open={visible}
|
||||
onOpenChange={(visible) => setVisible(visible)}
|
||||
>
|
||||
<Button {...buttonProps}>{text}</Button>
|
||||
</Popover>
|
||||
|
@ -74,6 +74,9 @@ export type WellTreeSelectorProps = TreeProps<TreeDataNode> & {
|
||||
show?: boolean
|
||||
expand?: boolean | Key[]
|
||||
current?: Key
|
||||
onClose?: () => void
|
||||
onChange?: (value: string | undefined) => void
|
||||
open?: boolean
|
||||
}
|
||||
|
||||
const getExpandKeys = (treeData: TreeDataNode[], depositKeys?: Key[] | boolean): Key[] => {
|
||||
@ -88,13 +91,11 @@ const getExpandKeys = (treeData: TreeDataNode[], depositKeys?: Key[] | boolean):
|
||||
return out
|
||||
}
|
||||
|
||||
export const WellTreeSelector = memo<WellTreeSelectorProps>(({ show, expand, current, ...other }) => {
|
||||
export const WellTreeSelector = memo<WellTreeSelectorProps>(({ expand, current, onChange, onClose, open, ...other }) => {
|
||||
const [wellsTree, setWellsTree] = useState<TreeDataNode[]>([])
|
||||
const [showLoader, setShowLoader] = useState<boolean>(false)
|
||||
const [visible, setVisible] = useState<boolean>(false)
|
||||
const [expanded, setExpanded] = useState<Key[]>([])
|
||||
const [selected, setSelected] = useState<Key[]>([])
|
||||
const [value, setValue] = useState<string>()
|
||||
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
@ -103,8 +104,6 @@ export const WellTreeSelector = memo<WellTreeSelectorProps>(({ show, expand, cur
|
||||
if (current) setSelected([current])
|
||||
}, [current])
|
||||
|
||||
useEffect(() => setVisible((prev) => show ?? prev), [show])
|
||||
|
||||
useEffect(() => {
|
||||
setExpanded((prev) => expand ? getExpandKeys(wellsTree, expand) : prev)
|
||||
}, [wellsTree, expand])
|
||||
@ -149,10 +148,10 @@ export const WellTreeSelector = memo<WellTreeSelectorProps>(({ show, expand, cur
|
||||
)
|
||||
}, [])
|
||||
|
||||
const onChange = useCallback((value?: string): void => {
|
||||
const onValueChange = useCallback((value?: string): void => {
|
||||
const key = getKeyByUrl(value)[0]
|
||||
setSelected(key ? [key] : [])
|
||||
setValue(getLabel(wellsTree, value))
|
||||
onChange?.(getLabel(wellsTree, value))
|
||||
}, [wellsTree])
|
||||
|
||||
const onSelect = useCallback((value: Key[]): void => {
|
||||
@ -170,13 +169,10 @@ export const WellTreeSelector = memo<WellTreeSelectorProps>(({ show, expand, cur
|
||||
navigate(newPath, { state: { from: location.pathname }})
|
||||
}, [navigate, location])
|
||||
|
||||
useEffect(() => onChange(location.pathname), [onChange, location])
|
||||
useEffect(() => onValueChange(location.pathname), [onValueChange, location])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button loading={showLoader} onClick={() => setVisible(true)}>{value ?? 'Выберите месторождение'}</Button>
|
||||
<Drawer visible={visible} mask={false} onClose={() => setVisible(false)}>
|
||||
<Typography.Title level={3}>Список скважин</Typography.Title>
|
||||
<Drawer open={open} mask={false} onClose={onClose} title={'Список скважин'}>
|
||||
<Skeleton active loading={showLoader}>
|
||||
<Tree
|
||||
{...other}
|
||||
@ -189,7 +185,6 @@ export const WellTreeSelector = memo<WellTreeSelectorProps>(({ show, expand, cur
|
||||
/>
|
||||
</Skeleton>
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Fragment, memo } from 'react'
|
||||
import { Tooltip } from 'antd'
|
||||
|
||||
import { TelemetryDto, TelemetryInfoDto } from '@api'
|
||||
import { Grid, GridItem } from '@components/Grid'
|
||||
import { formatDate } from '@utils'
|
||||
import { TelemetryDto, TelemetryInfoDto } from '@api'
|
||||
|
||||
export const lables: Record<string, string> = {
|
||||
timeZoneId: 'Временная зона',
|
||||
@ -30,12 +31,17 @@ export const TelemetryView = memo<TelemetryViewProps>(({ telemetry }) => telemet
|
||||
overlayInnerStyle={{ width: '400px' }}
|
||||
title={
|
||||
<Grid>
|
||||
{(Object.keys(telemetry.info) as Array<keyof TelemetryInfoDto>).map((key, i) => (
|
||||
{(Object.keys(telemetry.info) as Array<keyof TelemetryInfoDto>).map((key, i) => {
|
||||
let value = telemetry.info?.[key]
|
||||
value = key === 'drillingStartDate' ? formatDate(value) : value
|
||||
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<GridItem row={i+1} col={1}>{lables[key] ?? key}:</GridItem>
|
||||
<GridItem row={i+1} col={2}>{telemetry.info?.[key]}</GridItem>
|
||||
<GridItem row={i+1} col={2}>{value}</GridItem>
|
||||
</Fragment>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
}
|
||||
>
|
||||
|
@ -20,7 +20,7 @@ export const WidgetSettingsWindow = memo<WidgetSettingsWindowProps>(({ settings,
|
||||
return (
|
||||
<Modal
|
||||
{...other}
|
||||
visible={!!settings}
|
||||
open={!!settings}
|
||||
title={(
|
||||
<>
|
||||
Настройка виджета {settings?.label ? `"${settings?.label}"` : ''}
|
||||
|
@ -1,22 +1,53 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
import { createContext, useContext, useEffect } from 'react'
|
||||
|
||||
import { WellDto } from '@api'
|
||||
import { LayoutPortalProps } from '@components/LayoutPortal'
|
||||
import { UserTokenDto, WellDto } from '@api'
|
||||
|
||||
/** Контекст текущей скважины */
|
||||
export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}])
|
||||
/** Контекст текущего корневого пути */
|
||||
export const RootPathContext = createContext<string>('')
|
||||
export const RootPathContext = createContext<string>('/')
|
||||
/** Контекст текущего пользователя */
|
||||
export const UserContext = createContext<UserTokenDto>({})
|
||||
/** Контекст метода редактирования параметров заголовка и меню */
|
||||
export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {})
|
||||
/** Контекст для блока справа от крошек на страницах скважин и админки */
|
||||
export const TopRightBlockContext = createContext<(block: JSX.Element) => void>(() => {})
|
||||
|
||||
/**
|
||||
* Получение текущей скважины
|
||||
* Получить текущую скважину
|
||||
*
|
||||
* @returns Текущая скважина, либо `null`
|
||||
*/
|
||||
export const useWell = () => useContext(WellContext)
|
||||
|
||||
/**
|
||||
* Получает текущий корневой путь
|
||||
* Получить текущий корневой путь
|
||||
*
|
||||
* @returns Текущий корневой путь
|
||||
*/
|
||||
export const useRootPath = () => useContext(RootPathContext)
|
||||
|
||||
/**
|
||||
* Получить текущего пользователя
|
||||
*
|
||||
* @returns Текущий пользователь, либо `null`
|
||||
*/
|
||||
export const useUser = () => useContext(UserContext)
|
||||
|
||||
export const useTopRightBlock = () => useContext(TopRightBlockContext)
|
||||
|
||||
/**
|
||||
* Получить метод задания параметров заголовка и меню
|
||||
*
|
||||
* @returns Получить метод задания параметров заголовка и меню
|
||||
*/
|
||||
export const useLayoutProps = (props?: LayoutPortalProps) => {
|
||||
const setLayoutProps = useContext(LayoutPropsContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (props) setLayoutProps(props)
|
||||
}, [setLayoutProps, props])
|
||||
|
||||
return setLayoutProps
|
||||
}
|
||||
|
@ -1,11 +1,44 @@
|
||||
import { memo } from 'react'
|
||||
|
||||
import { ReactComponent as AsbLogo } from '@images/dd_logo_white_opt.svg'
|
||||
export type LogoProps = React.SVGProps<SVGSVGElement> & {
|
||||
size?: number
|
||||
onlyIcon?: boolean
|
||||
}
|
||||
|
||||
export type LogoProps = React.SVGProps<SVGSVGElement> & { size?: number }
|
||||
export const Logo = memo<LogoProps>(({ size = 170, onlyIcon, ...props }) => (
|
||||
<svg version={'1.1'} viewBox={`0 0 896 282`} fill={'#f3f6e8'} className={'logo'} style={{ width: size, height: 282/896*size, overflow: 'visible' }} {...props}>
|
||||
<g className={'logo-icon'}>
|
||||
<path fill={'#9e1937'} d={'m126 32.2h-92.5c-2.58 0-4.67-2.09-4.67-4.67s2.09-4.67 4.67-4.67h92.5c2.58 0 4.67 2.09 4.67 4.67s-2.09 4.67-4.67 4.67'} />
|
||||
<path d={'m30.5 274h98.3l-36.1-194h-26.2zm104 9.33h-110c-1.39 0-2.7-0.617-3.59-1.68-0.887-1.07-1.25-2.47-0.999-3.83l37.8-203c0.41-2.21 2.34-3.82 4.59-3.82h34c2.25 0 4.18 1.6 4.59 3.82l37.8 203c0.253 1.36-0.112 2.77-0.999 3.83-0.887 1.07-2.2 1.68-3.59 1.68'} />
|
||||
<path d={'m113 10.3h-66.9c-2.58 0-4.67-2.09-4.67-4.67 0-2.58 2.09-4.67 4.67-4.67h66.9c2.58 0 4.67 2.09 4.67 4.67 0 2.58-2.09 4.67-4.67 4.67'} />
|
||||
<path d={'m155 262c-2.17 0-4.12-1.53-4.57-3.74l-41.1-203h-58.8l-39.9 197h85.9l-44.2-33.2c-1.61-1.21-2.26-3.3-1.62-5.21 0.635-1.91 2.42-3.19 4.43-3.19h37.1l-34.6-28.7c-1.51-1.26-2.08-3.33-1.41-5.17 0.668-1.85 2.42-3.08 4.39-3.08h27.8l-25.3-25.5c-1.33-1.34-1.72-3.34-1-5.08 0.725-1.74 2.42-2.87 4.31-2.87h18.3l-16.8-19c-1.22-1.37-1.51-3.33-0.759-5.01 0.754-1.67 2.42-2.75 4.25-2.75h17.6c2.58 0 4.67 2.09 4.67 4.67s-2.09 4.67-4.67 4.67h-7.23l16.8 19c1.22 1.38 1.51 3.34 0.759 5.01-0.754 1.67-2.42 2.75-4.25 2.75h-17.4l25.3 25.5c1.33 1.34 1.72 3.34 1 5.08-0.724 1.74-2.42 2.87-4.31 2.87h-26.1l34.6 28.7c1.51 1.26 2.08 3.33 1.41 5.17-0.668 1.85-2.42 3.08-4.39 3.08h-36.1l44.2 33.2c1.61 1.21 2.26 3.3 1.62 5.21-0.635 1.91-2.42 3.19-4.43 3.19h-106c-1.4 0-2.73-0.629-3.61-1.71-0.886-1.09-1.24-2.51-0.961-3.88l41.8-206c0.441-2.18 2.35-3.74 4.57-3.74h66.5c2.22 0 4.13 1.56 4.57 3.74l41.8 206c0.512 2.53-1.12 4.99-3.65 5.5-0.312 0.0625-0.624 0.0948-0.932 0.0948'} />
|
||||
</g>
|
||||
|
||||
export const Logo = memo<LogoProps>(({ size = 200, ...props }) => (
|
||||
<AsbLogo className={'logo'} height={'100%'} {...props} />
|
||||
<g className={'logo-label'}>
|
||||
<path fill={'#9e1937'} d={'m316 140c2.76-2.67 5.01-1.71 5.01 2.13v30.3c0 3.84-3.14 6.98-6.98 6.98h-2.38c-3.84 0-6.98-3.14-6.98-6.98v-14.5c0-3.84 2.26-9.16 5.01-11.8l6.31-6.09'} />
|
||||
<path d={'m647 159c0 3.84-3.14 6.98-6.97 6.98h-3.84c-3.84 0-6.97-3.14-6.97-6.98v-118c0-3.84 3.14-6.98 6.97-6.98h3.84c3.84 0 6.97 3.14 6.97 6.98v118'} />
|
||||
<path d={'m707 144c0 3.84 3.14 6.97 6.98 6.97h52.7c3.84 0 6.98 3.14 6.98 6.97v1.84c0 3.84-3.14 6.98-6.98 6.98h-70.4c-3.84 0-6.97-3.14-6.97-6.98v-118c0-3.84 3.14-6.98 6.97-6.98h3.84c3.84 0 6.97 3.14 6.97 6.98v102'} />
|
||||
<path d={'m827 144c0 3.84 3.14 6.97 6.97 6.97h52.7c3.84 0 6.98 3.14 6.98 6.97v1.84c0 3.84-3.14 6.98-6.98 6.98h-70.4c-3.84 0-6.98-3.14-6.98-6.98v-118c0-3.84 3.14-6.98 6.98-6.98h3.84c3.84 0 6.98 3.14 6.98 6.98v102'} />
|
||||
<path d={'m279 101c0 33.2-15.2 49.9-39.4 49.9h-19.3c-3.84 0-6.97-3.14-6.97-6.97v-87.3c0-3.84 3.14-6.98 6.97-6.98h20.5c23 0 38.1 18.1 38.1 51.4zm18.3 1.09c0-29.6-12.9-67.7-56.1-67.7h-38.7c-3.84 0-6.98 3.14-6.98 6.98v118c0 3.84 3.14 6.98 6.98 6.98h39.4c34.3 0 55.4-26.1 55.4-64.1'} />
|
||||
<path d={'m432 101c0 33.2-15.2 49.9-39.4 49.9h-19.3c-3.84 0-6.97-3.14-6.97-6.97v-87.3c0-3.84 3.14-6.98 6.97-6.98h20.5c23 0 38.1 18.1 38.1 51.4zm18.3 1.09c0-29.6-12.9-67.7-56.1-67.7h-38.7c-3.84 0-6.98 3.14-6.98 6.98v118c0 3.84 3.14 6.98 6.98 6.98h39.4c34.3 0 55.4-26.1 55.4-64.1'} />
|
||||
<path d={'m539 94.6h-33c-3.84 0-6.98-3.14-6.98-6.98v-30.9c0-3.84 3.14-6.98 6.98-6.98h36.3c8.89 0 23.6 1.63 23.6 22 0 19.4-13.8 22.9-26.9 22.9zm26.9 6.9c8.35-4.9 18.3-12.2 18.3-31.6 0-27.8-21.8-35.4-43.4-35.4h-52.6c-3.84 0-6.98 3.14-6.98 6.98v118c0 3.84 3.14 6.97 6.98 6.97h3.84c3.84 0 6.97-3.14 6.97-6.97v-42.5c0-3.84 3.14-6.98 6.98-6.98h34.9c21.4 0 23.6 12.5 23.6 23.4 0.308 6.29 0 16.5 0 23.9s3.76 9.1 8.94 9.1h8.85v-40.1c0-18.5-10.3-20.7-16.3-24.7'} />
|
||||
<path d={'m220 256c-0.964 0-1.77-0.809-1.77-1.77v-2.85h-17.7c-0.962 0-1.77-0.806-1.77-1.77v-23.4c0-0.965 0.807-1.77 1.77-1.77h2.62c0.962 0 1.77 0.809 1.77 1.77v19.4h9.25v-19.4c0-0.965 0.807-1.77 1.77-1.77h2.62c0.964 0 1.77 0.809 1.77 1.77v19.4h1.89c0.962 0 1.77 0.807 1.77 1.77v6.86c0 0.962-0.809 1.77-1.77 1.77h-2.23'} />
|
||||
<path d={'m267 251c-0.964 0-1.77-0.806-1.77-1.77v-15l-11.2 15.6c-0.463 0.694-1.2 1.12-2.01 1.12h-2.39c-0.964 0-1.77-0.806-1.77-1.77v-23.4c0-0.965 0.807-1.77 1.77-1.77h2.62c0.962 0 1.77 0.809 1.77 1.77v15l11.2-15.6c0.463-0.691 1.19-1.12 2-1.12h2.39c0.964 0 1.77 0.809 1.77 1.77v23.4c0 0.965-0.809 1.77-1.77 1.77h-2.62'} />
|
||||
<path d={'m319 238c0-2.54-1.39-4.78-5.51-4.89v9.78c4.04-0.076 5.51-1.73 5.51-4.89zm-17.1 0c0 3.12 1.66 4.81 5.43 4.89v-9.78c-3.93 0.115-5.43 2.27-5.43 4.89zm11.6 12.4c0 0.965-0.807 1.77-1.77 1.77h-2.62c-0.962 0-1.77-0.809-1.77-1.77v-1.85c-7.2-0.0758-12-3.82-12-10.6 0-6.66 4.89-10.4 12-10.6v-1.08c0-0.965 0.812-1.77 1.77-1.77h2.62c0.964 0 1.77 0.809 1.77 1.77v1.08c7.01 0.158 12.1 3.97 12.1 10.6 0 6.7-4.89 10.5-12.1 10.6v1.85'} />
|
||||
<path d={'m356 236c0.886 0.115 1.92 0.194 2.81 0.194 1.62 0 3.62-0.694 3.62-3.31 0-2.39-1.54-3.12-3.74-3.12-0.807 0-1.42 0.0393-2.69 0.0788zm13-3.08c0 4.81-3.74 9.05-9.98 9.05-0.576 0-2.04 0-3-0.115v7.4c0 0.965-0.809 1.77-1.77 1.77h-2.62c-0.964 0-1.77-0.806-1.77-1.77v-23.4c0-1 0.807-1.81 1.77-1.81 2.04-0.0393 4.97-0.0758 6.47-0.0758 8.2 0 10.9 4.28 10.9 8.94'} />
|
||||
<path d={'m405 246c5.12 0 7.78-3.62 7.78-8.17 0-4.93-3.43-8.16-7.78-8.16-4.47 0-7.78 3.24-7.78 8.16 0 4.62 3.47 8.17 7.78 8.17zm0-22.1c8.2 0 14.3 5.35 14.3 13.9 0 8.17-6.13 13.9-14.3 13.9-8.21 0-14.3-5.35-14.3-13.9 0-7.82 5.74-13.9 14.3-13.9'} />
|
||||
<path d={'m450 246c0.423 0.115 0.923 0.232 2.08 0.232 2.39 0 3.58-1.04 3.58-2.85 0-1.7-1.27-2.43-3.27-2.43h-2.39zm2.04-10.4c1.58 0 2.85-0.654 2.85-2.62 0-1.62-1.46-2.43-2.96-2.43-0.77 0-1.23 0.0768-1.93 0.156v4.89zm5.93 1.93c1.96 0.771 3.85 2.73 3.85 6.2 0 5.66-4.39 8.32-10.2 8.32-1.85 0-4.2-0.0364-5.97-0.113-0.925-0.0393-1.77-0.923-1.77-1.85v-23.3c0-0.964 0.809-1.81 1.77-1.85 1.81-0.0759 4.32-0.155 6.39-0.155 6.43 0 9.05 2.97 9.05 6.7 0 2.81-1.08 4.7-3.08 6.05'} />
|
||||
<path d={'m500 246c5.12 0 7.78-3.62 7.78-8.17 0-4.93-3.43-8.16-7.78-8.16-4.47 0-7.78 3.24-7.78 8.16 0 4.62 3.47 8.17 7.78 8.17zm0-22.1c8.2 0 14.3 5.35 14.3 13.9 0 8.17-6.13 13.9-14.3 13.9-8.21 0-14.3-5.35-14.3-13.9 0-7.82 5.74-13.9 14.3-13.9'} />
|
||||
<path d={'m555 250c0 0.965-0.807 1.77-1.77 1.77h-12.8c-0.962 0-1.77-0.806-1.77-1.77v-23.4c0-0.965 0.809-1.77 1.77-1.77h12.4c0.962 0 1.77 0.809 1.77 1.77v2.23c0 0.96-0.809 1.77-1.77 1.77h-8.05v4.74h6.89c0.962 0 1.77 0.809 1.77 1.77v2.23c0 0.965-0.809 1.77-1.77 1.77h-6.89v4.89h8.43c0.964 0 1.77 0.807 1.77 1.77v2.23'} />
|
||||
<path d={'m613 246c1 0.116 1.62 0.192 2.31 0.192 2.54 0 3.35-1.27 3.35-2.78 0-1.58-0.849-3-3.08-3-0.77 0-1.66 0.077-2.58 0.232zm3.08-11.4c5.2 0 8.74 3.24 8.74 8.32 0 5.62-3.74 9.01-10.5 9.01-2.39 0-4.28-0.0758-5.74-0.153-1-0.0393-1.77-0.846-1.77-1.85v-23.4c0-0.965 0.809-1.77 1.77-1.77h12.7c0.962 0 1.77 0.809 1.77 1.77v2.23c0 0.96-0.809 1.77-1.77 1.77h-8.32v4.28c0.733-0.157 2.23-0.233 3.08-0.233'} />
|
||||
<path d={'m661 238 4.93-11.9c0.347-0.771 1.08-1.27 1.89-1.27h2.2c1.04 0 1.73 0.733 1.73 1.66 0 0.268-0.076 0.538-0.192 0.807l-7.93 18.1c-1.81 4.12-4.16 6.39-7.9 6.39-1.04 0-2.12-0.191-3.16-0.654-0.541-0.268-0.886-0.733-0.886-1.42 0-0.233 0.037-0.502 0.153-0.809l0.733-1.89c0.347-0.923 0.925-1.2 1.62-1.2 0.231 0 0.463 0.0393 0.694 0.0759 0.502 0.118 0.846 0.118 1 0.118 0.809 0 1.46-0.31 1.81-1.08l0.347-0.809-9.98-16.6c-0.192-0.35-0.268-0.697-0.268-1.04 0-0.889 0.615-1.66 1.69-1.66h2.58c0.807 0 1.62 0.462 2.04 1.19l6.89 12'} />
|
||||
<path d={'m702 236c0.886 0.115 1.92 0.194 2.81 0.194 1.62 0 3.62-0.694 3.62-3.31 0-2.39-1.54-3.12-3.74-3.12-0.807 0-1.42 0.0393-2.69 0.0788zm13-3.08c0 4.81-3.74 9.05-9.98 9.05-0.578 0-2.04 0-3-0.115v7.4c0 0.965-0.809 1.77-1.77 1.77h-2.62c-0.964 0-1.77-0.806-1.77-1.77v-23.4c0-1 0.807-1.81 1.77-1.81 2.04-0.0393 4.97-0.0758 6.47-0.0758 8.2 0 10.9 4.28 10.9 8.94'} />
|
||||
<path d={'m756 250c0 0.965-0.807 1.77-1.77 1.77h-12.8c-0.962 0-1.77-0.806-1.77-1.77v-23.4c0-0.965 0.809-1.77 1.77-1.77h12.4c0.962 0 1.77 0.809 1.77 1.77v2.23c0 0.96-0.809 1.77-1.77 1.77h-8.05v4.74h6.89c0.962 0 1.77 0.809 1.77 1.77v2.23c0 0.965-0.809 1.77-1.77 1.77h-6.89v4.89h8.43c0.964 0 1.77 0.807 1.77 1.77v2.23'} />
|
||||
<path d={'m803 250c0 0.965-0.809 1.77-1.77 1.77h-2.62c-0.964 0-1.77-0.806-1.77-1.77v-9.09h-9.82v9.09c0 0.965-0.812 1.77-1.77 1.77h-2.62c-0.964 0-1.77-0.806-1.77-1.77v-23.4c0-0.965 0.807-1.77 1.77-1.77h2.62c0.962 0 1.77 0.809 1.77 1.77v8.55h9.82v-8.55c0-0.965 0.807-1.77 1.77-1.77h2.62c0.964 0 1.77 0.809 1.77 1.77v23.4'} />
|
||||
<path d={'m849 251c-0.962 0-1.77-0.806-1.77-1.77v-15l-11.2 15.6c-0.463 0.694-1.19 1.12-2 1.12h-2.39c-0.962 0-1.77-0.806-1.77-1.77v-23.4c0-0.965 0.809-1.77 1.77-1.77h2.62c0.962 0 1.77 0.809 1.77 1.77v15l11.2-15.6c0.463-0.691 1.2-1.12 2-1.12h2.39c0.962 0 1.77 0.809 1.77 1.77v23.4c0 0.965-0.809 1.77-1.77 1.77h-2.62'} />
|
||||
<path d={'m896 250c0 0.965-0.809 1.77-1.77 1.77h-12.8c-0.964 0-1.77-0.806-1.77-1.77v-23.4c0-0.965 0.807-1.77 1.77-1.77h12.4c0.962 0 1.77 0.809 1.77 1.77v2.23c0 0.96-0.809 1.77-1.77 1.77h-8.05v4.74h6.89c0.962 0 1.77 0.809 1.77 1.77v2.23c0 0.965-0.809 1.77-1.77 1.77h-6.89v4.89h8.43c0.962 0 1.77 0.807 1.77 1.77v2.23'} />
|
||||
</g>
|
||||
</svg>
|
||||
))
|
||||
|
||||
export default Logo
|
||||
|
46
src/pages/AdminPanel/AdminNavigationMenu.jsx
Normal file
46
src/pages/AdminPanel/AdminNavigationMenu.jsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
ApiOutlined,
|
||||
BankOutlined,
|
||||
BranchesOutlined,
|
||||
DashboardOutlined,
|
||||
FileSearchOutlined,
|
||||
FolderOutlined,
|
||||
IdcardOutlined,
|
||||
MonitorOutlined,
|
||||
TeamOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
import { makeItem, PrivateWellMenu } from '@components/PrivateWellMenu'
|
||||
import { isDev } from '@utils'
|
||||
|
||||
export const menuItems = [
|
||||
makeItem('Месторождения', 'deposit', [], <FolderOutlined />),
|
||||
makeItem('Кусты', 'cluster', [], <FolderOutlined />),
|
||||
makeItem('Скважины', 'well', [], <FolderOutlined />),
|
||||
makeItem('Пользователи', 'user', [], <UserOutlined />),
|
||||
makeItem('Компании', 'company', [], <BankOutlined />),
|
||||
makeItem('Типы компаний', 'company_type', [], <BankOutlined />),
|
||||
makeItem('Роли', 'role', [], <TeamOutlined />),
|
||||
makeItem('Разрешения', 'permission', [], <IdcardOutlined />),
|
||||
makeItem('Телеметрия', 'telemetry', [], <DashboardOutlined />, [
|
||||
makeItem('Просмотр', 'viewer', [], <MonitorOutlined />),
|
||||
makeItem('Объединение', 'merger', [], <BranchesOutlined />),
|
||||
]),
|
||||
makeItem('Журнал посещений', 'visit_log', [], <FileSearchOutlined />),
|
||||
isDev() && makeItem('API', '/swagger/index.html', [], <ApiOutlined />),
|
||||
].filter(Boolean)
|
||||
|
||||
export const AdminNavigationMenu = memo((props) => (
|
||||
<PrivateWellMenu
|
||||
{...props}
|
||||
items={menuItems}
|
||||
rootPath={'/admin'}
|
||||
selectable={false}
|
||||
mode={'inline'}
|
||||
theme={'dark'}
|
||||
/>
|
||||
))
|
||||
|
||||
export default AdminNavigationMenu
|
@ -11,11 +11,9 @@ import {
|
||||
} from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminClusterService, AdminDepositService } from '@api'
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
import { arrayOrDefault, coordsFormat, wrapPrivateComponent } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
|
||||
import { coordsFixed } from './DepositController'
|
||||
|
||||
const ClusterController = memo(() => {
|
||||
const [deposits, setDeposits] = useState([])
|
||||
const [clusters, setClusters] = useState([])
|
||||
@ -41,8 +39,8 @@ const ClusterController = memo(() => {
|
||||
sorter: makeStringSorter('caption'),
|
||||
formItemRules: min1,
|
||||
}),
|
||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFormat }),
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFormat }),
|
||||
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
||||
], [deposits])
|
||||
|
||||
@ -108,6 +106,7 @@ const ClusterController = memo(() => {
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
tableName={'admin_cluster_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -97,6 +97,7 @@ const CompanyController = memo(() => {
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
tableName={'admin_company_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -75,6 +75,7 @@ const CompanyTypeController = memo(() => {
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
tableName={'admin_company_type_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -3,16 +3,14 @@ import { Input } from 'antd'
|
||||
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { EditableTable, makeColumn, defaultPagination, makeTimezoneColumn } from '@components/Table'
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
import { arrayOrDefault, coordsFormat, wrapPrivateComponent } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
import { AdminDepositService } from '@api'
|
||||
|
||||
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
|
||||
|
||||
const depositColumns = [
|
||||
makeColumn('Название', 'caption', { width: 200, editable: true, formItemRules: min1 }),
|
||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFormat }),
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFormat }),
|
||||
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
||||
]
|
||||
|
||||
@ -77,6 +75,7 @@ const DepositController = memo(() => {
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
tableName={'admin_deposit_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -82,6 +82,7 @@ const PermissionController = memo(() => {
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
tableName={'admin_permission_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -89,6 +89,7 @@ const RoleController = memo(() => {
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
tableName={'admin_role_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ import { Button, Input } from 'antd'
|
||||
import {
|
||||
defaultPagination,
|
||||
makeColumn,
|
||||
makeDateSorter,
|
||||
makeDateColumn,
|
||||
makeNumericColumn,
|
||||
makeNumericRender,
|
||||
makeTextColumn,
|
||||
@ -53,7 +53,7 @@ const TelemetryController = memo(() => {
|
||||
makeNumericColumn('ID', 'id', null, null, makeNumericRender(0)),
|
||||
makeTextColumn('UID', 'remoteUid'),
|
||||
makeTextColumn('Назначена на скважину', 'realWell'),
|
||||
makeTextColumn('Дата начала бурения', 'drillingStartDate', null, makeDateSorter('drillingStartDate')),
|
||||
makeDateColumn('Дата начала бурения', 'drillingStartDate'),
|
||||
makeTextColumn('Часовой пояс', 'timeZoneId'),
|
||||
makeTextColumn('Скважина', 'well'),
|
||||
makeTextColumn('Куст', 'cluster'),
|
||||
@ -115,6 +115,7 @@ const TelemetryController = memo(() => {
|
||||
pagination={defaultPagination}
|
||||
dataSource={filteredTelemetryData}
|
||||
tableName={'admin_telemetry_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -1,13 +1,8 @@
|
||||
import { Layout } from 'antd'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import TelemetryViewer from './TelemetryViewer'
|
||||
import TelemetryMerger from './TelemetryMerger'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
|
||||
const Telemetry = memo(() => {
|
||||
const root = useRootPath()
|
||||
@ -15,23 +10,7 @@ const Telemetry = memo(() => {
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu>
|
||||
<PrivateMenu.Link content={TelemetryViewer} />
|
||||
<PrivateMenu.Link content={TelemetryMerger} />
|
||||
</PrivateMenu>
|
||||
|
||||
<Layout>
|
||||
<Layout.Content className={'site-layout-background'}>
|
||||
<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>
|
||||
<Outlet />
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
@ -214,6 +214,7 @@ const UserController = memo(() => {
|
||||
buttonsWidth={120}
|
||||
pagination={defaultPagination}
|
||||
tableName={'admin_user_controller'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
<ChangePassword
|
||||
|
@ -59,6 +59,7 @@ const VisitLog = memo(() => {
|
||||
dataSource={filteredLogData}
|
||||
pagination={defaultPagination}
|
||||
tableName={'visit_log'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -21,9 +21,7 @@ import {
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { TelemetryView, CompanyView } from '@components/views'
|
||||
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import { coordsFixed } from '../DepositController'
|
||||
import { arrayOrDefault, coordsFormat, wrapPrivateComponent } from '@utils'
|
||||
|
||||
const wellTypes = [
|
||||
{ value: 1, label: 'Наклонно-направленная' },
|
||||
@ -98,8 +96,8 @@ const WellController = memo(() => {
|
||||
editable: true,
|
||||
sorter: makeNumericSorter('idWellType'),
|
||||
}),
|
||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFormat }),
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFormat }),
|
||||
makeColumn('Телеметрия', 'telemetry', {
|
||||
editable: true,
|
||||
render: (telemetry) => <TelemetryView telemetry={telemetry} />,
|
||||
|
@ -1,69 +1,61 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Layout } from 'antd'
|
||||
import { lazy, memo, useMemo } from 'react'
|
||||
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { AdminLayoutPortal } from '@components/Layout'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context'
|
||||
import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
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'
|
||||
import { AdminNavigationMenu, menuItems } from './AdminNavigationMenu'
|
||||
|
||||
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 Telemetry = lazy(() => import('./Telemetry'))
|
||||
const TelemetryViewer = lazy(() => import('./Telemetry/TelemetryViewer'))
|
||||
const TelemetryMerger = lazy(() => import('./Telemetry/TelemetryMerger'))
|
||||
const VisitLog = lazy(() => import('./VisitLog'))
|
||||
|
||||
const layoutProps = {
|
||||
sider: <AdminNavigationMenu />,
|
||||
title: 'Администраторская панель',
|
||||
isAdmin: true,
|
||||
breadcrumb: <MenuBreadcrumbItems menuItems={menuItems} pathRoot={/^\/admin\//} />,
|
||||
}
|
||||
|
||||
const AdminPanel = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/admin`, [root])
|
||||
|
||||
useLayoutProps(layoutProps)
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<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'}>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={VisitLog.route} replace />} />
|
||||
<Route index element={<Navigate to={'visit_log'} 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 />} />
|
||||
<Route path={'deposit'} element={<DepositController />} />
|
||||
<Route path={'cluster'} element={<ClusterController />} />
|
||||
<Route path={'well'} element={<WellController />} />
|
||||
<Route path={'user'} element={<UserController />} />
|
||||
<Route path={'company'} element={<CompanyController />} />
|
||||
<Route path={'company_type'} element={<CompanyTypeController />} />
|
||||
<Route path={'role'} element={<RoleController />} />
|
||||
<Route path={'permission'} element={<PermissionController />} />
|
||||
<Route path={'telemetry'} element={<Telemetry />}>
|
||||
<Route index element={<Navigate to={'viewer'} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
<Route path={'viewer'} element={<TelemetryViewer />} />
|
||||
<Route path={'merger'} element={<TelemetryMerger />} />
|
||||
</Route>
|
||||
<Route path={'visit_log'} element={<VisitLog />} />
|
||||
</Routes>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</AdminLayoutPortal>
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(AdminPanel, {
|
||||
requirements: ['RequestTracker.get'],
|
||||
title: 'Панель администратора',
|
||||
route: 'admin/*',
|
||||
key: 'admin',
|
||||
})
|
||||
export default wrapPrivateComponent(AdminPanel, { requirements: ['RequestTracker.get'] })
|
||||
|
@ -1,44 +0,0 @@
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { Layout } from 'antd'
|
||||
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import Statistics from './Statistics'
|
||||
import WellCompositeEditor from './WellCompositeEditor'
|
||||
|
||||
const Analytics = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/analytics`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu className={'well_menu'}>
|
||||
<PrivateMenu.Link content={WellCompositeEditor} />
|
||||
<PrivateMenu.Link key={'statistics'} title={'Оценка по ЦБ'} content={Statistics} />
|
||||
</PrivateMenu>
|
||||
<Layout>
|
||||
<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>
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(Analytics, {
|
||||
requirements: [],
|
||||
title: 'Аналитика',
|
||||
route: 'analytics/*',
|
||||
key: 'analytics',
|
||||
})
|
@ -1,6 +1,6 @@
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useState, useEffect, memo, useMemo } from 'react'
|
||||
import { Tag, Button, Modal } from 'antd'
|
||||
import { useState, useEffect, memo, useMemo, lazy, Suspense } from 'react'
|
||||
import { Button, Modal } from 'antd'
|
||||
import { LineChartOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons'
|
||||
|
||||
import {
|
||||
@ -13,9 +13,9 @@ import {
|
||||
makeNumericRender,
|
||||
makeNumericColumn,
|
||||
} from '@components/Table'
|
||||
import { CompanyView } from '@components/views'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import PointerIcon from '@components/icons/PointerIcon'
|
||||
import SuspenseFallback from '@components/SuspenseFallback'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import {
|
||||
getOperations,
|
||||
@ -25,9 +25,10 @@ import {
|
||||
wrapPrivateComponent
|
||||
} from '@utils'
|
||||
|
||||
import Tvd from '@pages/WellOperations/Tvd'
|
||||
const Tvd = lazy(() => import('@pages/Well/WellOperations/Tvd'))
|
||||
|
||||
import CompaniesTable from './CompaniesTable'
|
||||
import WellOperationsTable from './WellOperationsTable'
|
||||
import CompaniesTable from '@pages/Cluster/CompaniesTable'
|
||||
|
||||
const filtersMinMax = [
|
||||
{ text: 'min', value: 'min' },
|
||||
@ -173,23 +174,26 @@ const ClusterWells = memo(({ statsWells }) => {
|
||||
pagination={false}
|
||||
rowKey={(record) => record.caption}
|
||||
tableName={'cluster'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={'TVD'}
|
||||
centered
|
||||
visible={isTVDModalVisible}
|
||||
open={isTVDModalVisible}
|
||||
onCancel={() => setIsTVDModalVisible(false)}
|
||||
width={1500}
|
||||
footer={null}
|
||||
>
|
||||
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
|
||||
<Tvd style={{ minHeight: '600px' }} well={selectedWell} />
|
||||
</Suspense>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={'Операции'}
|
||||
centered
|
||||
visible={isOpsModalVisible}
|
||||
open={isOpsModalVisible}
|
||||
onCancel={() => setIsOpsModalVisible(false)}
|
||||
width={1500}
|
||||
footer={null}
|
||||
@ -202,7 +206,7 @@ const ClusterWells = memo(({ statsWells }) => {
|
||||
<Modal
|
||||
title={'Участники'}
|
||||
centered
|
||||
visible={isCompaniesModalVisible}
|
||||
open={isCompaniesModalVisible}
|
||||
onCancel={() => setIsCompaniesModalVisible(false)}
|
||||
width={1500}
|
||||
footer={null}
|
||||
|
@ -1,18 +1,22 @@
|
||||
import React, { memo, useMemo } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { BankOutlined } from '@ant-design/icons'
|
||||
|
||||
import { makeTextColumn, Table } from '@components/Table'
|
||||
import { makeColumn, makeTextColumn, Table } from '@components/Table'
|
||||
|
||||
const columns = [
|
||||
makeTextColumn('', 'logo'),
|
||||
makeColumn('', 'logo'),
|
||||
makeTextColumn('Название компании', 'caption'),
|
||||
makeTextColumn('Тип компании', 'companyTypeCaption'),
|
||||
]
|
||||
|
||||
const CompaniesTable = memo(({companies}) => {
|
||||
const CompaniesTable = memo(({ companies }) => {
|
||||
const dataCompanies = useMemo(() => companies?.map((company) => ({
|
||||
key: company.id,
|
||||
logo: company?.logo ? <img src={company.logo}/> : <BankOutlined/>,
|
||||
logo: (
|
||||
<div className={'centered'}>
|
||||
{company?.logo ? <img src={company.logo}/> : <BankOutlined/>}
|
||||
</div>
|
||||
),
|
||||
caption: company.caption,
|
||||
companyTypeCaption: company.companyTypeCaption,
|
||||
})), [companies])
|
||||
|
@ -7,8 +7,8 @@ import { getPrecision } from '@utils/functions'
|
||||
const columns = [
|
||||
makeTextColumn('Конструкция секции', 'sectionType'),
|
||||
makeTextColumn('Операция', 'operationName'),
|
||||
makeNumericColumnPlanFact('Глубина забоя', 'depth', null, null, getPrecision),
|
||||
makeNumericColumnPlanFact('Часы', 'durationHours', null, null, getPrecision),
|
||||
makeNumericColumnPlanFact('Глубина забоя', 'depth', null, null, (number) => getPrecision(number)),
|
||||
makeNumericColumnPlanFact('Часы', 'durationHours', null, null, (number) => getPrecision(number)),
|
||||
makeNumericColumnPlanFact('Комментарий', 'comment', null, null, (text) => text ?? '-')
|
||||
]
|
||||
|
||||
|
@ -1,19 +1,26 @@
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { LayoutPortal } from '@components/Layout'
|
||||
import { useLayoutProps } from '@asb/context'
|
||||
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'
|
||||
|
||||
const layoutProps = {
|
||||
title: 'Анализ скважин куста',
|
||||
breadcrumb: true,
|
||||
}
|
||||
|
||||
const Cluster = memo(() => {
|
||||
const { idCluster } = useParams()
|
||||
const [data, setData] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
useLayoutProps(layoutProps)
|
||||
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -27,11 +34,9 @@ const Cluster = memo(() => {
|
||||
}, [idCluster])
|
||||
|
||||
return (
|
||||
<LayoutPortal title={'Анализ скважин куста'}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<ClusterWells statsWells={data} />
|
||||
</LoaderPortal>
|
||||
</LayoutPortal>
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -3,8 +3,8 @@ import { useState, useEffect, memo, useMemo } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Popover, Badge } from 'antd'
|
||||
|
||||
import { useLayoutProps } from '@asb/context'
|
||||
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'
|
||||
@ -47,18 +47,26 @@ const Deposit = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [viewParams, setViewParams] = useState(defaultViewParams)
|
||||
|
||||
const setLayoutProps = useLayoutProps()
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
const selectorProps = useMemo(() => {
|
||||
const hasId = location.pathname.length > '/deposit/'.length
|
||||
|
||||
return {
|
||||
show: true,
|
||||
expand: hasId ? [location.pathname] : true,
|
||||
current: hasId ? location.pathname : undefined,
|
||||
}
|
||||
}, [location.pathname])
|
||||
|
||||
useEffect(() => setLayoutProps({
|
||||
sheet: false,
|
||||
showSelector: true,
|
||||
selectorProps,
|
||||
title: 'Месторождение',
|
||||
}), [setLayoutProps, selectorProps])
|
||||
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -73,9 +81,8 @@ const Deposit = memo(() => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<LayoutPortal noSheet selector={selectorProps} title={'Месторождение'}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<div className={'h-100vh'}>
|
||||
<div className={'h-100vh'} style={{ overflow: 'hidden' }}>
|
||||
<Map {...viewParams}>
|
||||
{depositsData.map(deposit => (
|
||||
<Overlay
|
||||
@ -103,7 +110,6 @@ const Deposit = memo(() => {
|
||||
</Map>
|
||||
</div>
|
||||
</LoaderPortal>
|
||||
</LayoutPortal>
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -7,8 +7,6 @@ import { downloadFile, invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
import { FileService } from '@api'
|
||||
|
||||
import AccessDenied from './AccessDenied'
|
||||
|
||||
const { Paragraph, Text } = Typography
|
||||
|
||||
export const getLinkToFile = (fileInfo) => `/file_download/${fileInfo.id}`
|
||||
@ -97,4 +95,4 @@ FileDownload.displayName = 'FileDownloadMemo'
|
||||
export default wrapPrivateComponent(FileDownload, {
|
||||
requirements: ['File.get'],
|
||||
route: 'file_download/:idFile/*',
|
||||
}, <AccessDenied />)
|
||||
})
|
||||
|
@ -1,48 +0,0 @@
|
||||
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, 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
|
||||
|
||||
const Reports = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/reports`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu className={'well_menu'}>
|
||||
<PrivateMenu.Link content={DiagramReport} icon={<FilePdfOutlined />} />
|
||||
<PrivateMenu.Link content={DailyReport} />
|
||||
</PrivateMenu>
|
||||
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<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>
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(Reports, {
|
||||
requirements: [],
|
||||
title: 'Рапорта',
|
||||
route: 'reports/*',
|
||||
key: 'reports',
|
||||
})
|
@ -1,63 +0,0 @@
|
||||
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, 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 OperationTime from './OperationTime'
|
||||
|
||||
import '@styles/index.css'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
const Telemetry = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/telemetry`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<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.Link content={OperationTime} />
|
||||
</PrivateMenu>
|
||||
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<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 />} />
|
||||
<Route path={OperationTime.route} element={<OperationTime />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(Telemetry, {
|
||||
requirements: [],
|
||||
icon: <FundViewOutlined />,
|
||||
title: 'Телеметрия',
|
||||
route: 'telemetry/*',
|
||||
key: 'telemetry',
|
||||
})
|
@ -1,104 +0,0 @@
|
||||
import {
|
||||
FolderOutlined,
|
||||
FilePdfOutlined,
|
||||
ExperimentOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { Layout } from 'antd'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Navigate, Route, Routes, useParams } from 'react-router-dom'
|
||||
|
||||
import { WellContext, RootPathContext, useRootPath } from '@asb/context'
|
||||
import { LayoutPortal } from '@components/Layout'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
import { WellService } from '@api'
|
||||
|
||||
import Measure from './Measure'
|
||||
import Reports from './Reports'
|
||||
import WellCase from './WellCase'
|
||||
import Analytics from './Analytics'
|
||||
import Documents from './Documents'
|
||||
import Telemetry from './Telemetry'
|
||||
import WellOperations from './WellOperations'
|
||||
import DrillingProgram from './DrillingProgram'
|
||||
|
||||
import '@styles/index.css'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
const Well = memo(() => {
|
||||
const { idWell } = useParams()
|
||||
|
||||
const [well, setWell] = useState({ id: idWell })
|
||||
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell])
|
||||
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const well = await WellService.get(idWell)
|
||||
setWell(well ?? { id: idWell })
|
||||
},
|
||||
undefined,
|
||||
'Не удалось получить данные по скважине'
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
const updateWell = useCallback((data) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const newWell = { ...well, ...data }
|
||||
await WellService.updateWell(newWell)
|
||||
setWell(newWell)
|
||||
},
|
||||
undefined,
|
||||
`Не удалось изменить данные скважины`,
|
||||
{ actionName: 'Изменение данных скважины', well }
|
||||
), [well])
|
||||
|
||||
return (
|
||||
<LayoutPortal>
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<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.Link content={WellCase} icon={<FolderOutlined />} />
|
||||
</PrivateMenu>
|
||||
|
||||
<WellContext.Provider value={[well, updateWell]}>
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<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 />} />
|
||||
<Route path={WellCase.route} element={<WellCase />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</WellContext.Provider>
|
||||
</RootPathContext.Provider>
|
||||
</LayoutPortal>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(Well, {
|
||||
requirements: [],
|
||||
title: 'Скважина',
|
||||
route: 'well/:idWell/*',
|
||||
key: 'well',
|
||||
})
|
0
src/pages/Analytics/Statistics.jsx → src/pages/Well/Analytics/Statistics.jsx
Executable file → Normal file
0
src/pages/Analytics/Statistics.jsx → src/pages/Well/Analytics/Statistics.jsx
Executable file → Normal file
71
src/pages/Analytics/WellCompositeEditor/NewParamsTable.jsx → src/pages/Well/Analytics/WellCompositeEditor/NewParamsTable.jsx
Executable file → Normal file
71
src/pages/Analytics/WellCompositeEditor/NewParamsTable.jsx → src/pages/Well/Analytics/WellCompositeEditor/NewParamsTable.jsx
Executable file → Normal file
@ -2,12 +2,75 @@ import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { Button, Modal, Popconfirm } from 'antd'
|
||||
|
||||
import { useWell } from '@asb/context'
|
||||
import { Table } from '@components/Table'
|
||||
import { makeColumn, makeGroupColumn, makeNumericRender, makeSelectColumn, Table } from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DrillParamsService } from '@api'
|
||||
import { DrillParamsService, WellOperationService } from '@api'
|
||||
|
||||
import { getColumns } from '@pages/WellOperations/WellDrillParams'
|
||||
const getDeepValue = (data, key) => {
|
||||
if (!key || key.trim() === '') return null
|
||||
const keys = key.split('.')
|
||||
let out = data
|
||||
while (keys.length > 0) {
|
||||
if (!(keys[0] in out)) return null
|
||||
out = out[keys[0]]
|
||||
keys.splice(0, 1)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const makeNumericSorter = (keys) => (a, b) => getDeepValue(a, keys) - getDeepValue(b, keys)
|
||||
|
||||
const numericRender = makeNumericRender(1)
|
||||
const makeNumericColumn = (title, dataIndex, render, other) => makeColumn(title, dataIndex, {
|
||||
sorter: makeNumericSorter(dataIndex),
|
||||
render: (_, record, index) => {
|
||||
const func = render ?? ((value) => <>{value}</>)
|
||||
const item = getDeepValue(record, dataIndex)
|
||||
return func(item, record, index)
|
||||
},
|
||||
align: 'right',
|
||||
...other,
|
||||
})
|
||||
|
||||
const makeAvgRender = (dataIndex) => (avg, record) => {
|
||||
const max = record[dataIndex]?.max
|
||||
const fillW = (max - avg) / max * 100
|
||||
return (
|
||||
<div className={'avg-column'}>
|
||||
<div className={'avg-fill'} style={{ width: `${fillW}%` }} />
|
||||
<div className={'avg-value'}>
|
||||
{numericRender(avg)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const makeNumericAvgRange = (title, dataIndex, defaultRender = false) => makeGroupColumn(title, [
|
||||
makeNumericColumn('мин', `${dataIndex}.min`),
|
||||
makeNumericColumn('сред', `${dataIndex}.avg`, defaultRender ? undefined : makeAvgRender(dataIndex)),
|
||||
makeNumericColumn('макс', `${dataIndex}.max`),
|
||||
])
|
||||
|
||||
export const getColumns = async (idWell) => {
|
||||
let sectionTypes = await WellOperationService.getSectionTypes(idWell)
|
||||
sectionTypes = Object.entries(sectionTypes).map(([id, value]) => ({
|
||||
label: value,
|
||||
value: id,
|
||||
}))
|
||||
|
||||
return [
|
||||
makeSelectColumn('Конструкция секции','idWellSectionType', sectionTypes, null, {
|
||||
width: 160,
|
||||
sorter: makeNumericSorter('idWellSectionType'),
|
||||
}),
|
||||
makeNumericAvgRange('Нагрузка, т', 'axialLoad'),
|
||||
makeNumericAvgRange('Давление, атм', 'pressure'),
|
||||
makeNumericAvgRange('Момент на ВСП, кН·м', 'rotorTorque', true),
|
||||
makeNumericAvgRange('Обороты на ВСП, об/мин', 'rotorSpeed'),
|
||||
makeNumericAvgRange('Расход, л/с', 'flow'),
|
||||
]
|
||||
}
|
||||
|
||||
export const NewParamsTable = memo(({ selectedWellsKeys }) => {
|
||||
const [params, setParams] = useState([])
|
||||
@ -54,7 +117,7 @@ export const NewParamsTable = memo(({ selectedWellsKeys }) => {
|
||||
<Modal
|
||||
title={'Заполнить режимы текущей скважины'}
|
||||
centered
|
||||
visible={isParamsModalVisible}
|
||||
open={isParamsModalVisible}
|
||||
onCancel={() => setIsParamsModalVisible(false)}
|
||||
width={1700}
|
||||
footer={(
|
@ -1,7 +1,7 @@
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useState, useEffect, memo, useMemo } from 'react'
|
||||
import { useState, useEffect, memo, useMemo, lazy, Suspense } from 'react'
|
||||
import { LineChartOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons'
|
||||
import { Table, Tag, Button, Badge, Divider, Modal, Row, Col } from 'antd'
|
||||
import { Table, Button, Badge, Divider, Modal, Row, Col } from 'antd'
|
||||
|
||||
import { useWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
@ -16,10 +16,12 @@ import {
|
||||
getOperations
|
||||
} from '@utils'
|
||||
|
||||
import Tvd from '@pages/WellOperations/Tvd'
|
||||
import WellOperationsTable from '@pages/Cluster/WellOperationsTable'
|
||||
import NewParamsTable from './NewParamsTable'
|
||||
import CompaniesTable from '@pages/Cluster/CompaniesTable'
|
||||
import SuspenseFallback from '@asb/components/SuspenseFallback'
|
||||
|
||||
const Tvd = lazy(() => import('@pages/Well/WellOperations/Tvd'))
|
||||
const CompaniesTable = lazy(() => import('@pages/Cluster/CompaniesTable'))
|
||||
const WellOperationsTable = lazy(() => import('@pages/Cluster/WellOperationsTable'))
|
||||
|
||||
const filtersMinMax = [
|
||||
{ text: 'min', value: 'min' },
|
||||
@ -222,38 +224,44 @@ const WellCompositeSections = memo(({ statsWells, selectedSections }) => {
|
||||
<Modal
|
||||
title={'TVD'}
|
||||
centered
|
||||
visible={isTVDModalVisible}
|
||||
open={isTVDModalVisible}
|
||||
onCancel={() => setIsTVDModalVisible(false)}
|
||||
width={1500}
|
||||
footer={null}
|
||||
>
|
||||
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
|
||||
<Tvd well={selectedWell} style={{ height: '80vh' }} />
|
||||
</Suspense>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={'Операции'}
|
||||
centered
|
||||
visible={isOpsModalVisible}
|
||||
open={isOpsModalVisible}
|
||||
onCancel={() => setIsOpsModalVisible(false)}
|
||||
width={1500}
|
||||
footer={null}
|
||||
>
|
||||
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<WellOperationsTable wellOperations={wellOperations} />
|
||||
</LoaderPortal>
|
||||
</Suspense>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={'Участники'}
|
||||
centered
|
||||
visible={isCompaniesModalVisible}
|
||||
open={isCompaniesModalVisible}
|
||||
onCancel={() => setIsCompaniesModalVisible(false)}
|
||||
width={1500}
|
||||
footer={null}
|
||||
>
|
||||
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<CompaniesTable companies={companies} />
|
||||
</LoaderPortal>
|
||||
</Suspense>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
49
src/pages/Analytics/WellCompositeEditor/index.jsx → src/pages/Well/Analytics/WellCompositeEditor/index.jsx
Executable file → Normal file
49
src/pages/Analytics/WellCompositeEditor/index.jsx → src/pages/Well/Analytics/WellCompositeEditor/index.jsx
Executable file → Normal file
@ -1,35 +1,26 @@
|
||||
import { useState, useEffect, memo, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { Col, Layout, Row } from 'antd'
|
||||
import { useState, useEffect, memo, Suspense, lazy } from 'react'
|
||||
import { Row } from 'antd'
|
||||
|
||||
import { useWell, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { useWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import SuspenseFallback from '@components/SuspenseFallback'
|
||||
import WellSelector from '@components/selectors/WellSelector'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault, NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
import { OperationStatService, WellCompositeService } from '@api'
|
||||
|
||||
import ClusterWells from '@pages/Cluster/ClusterWells'
|
||||
import WellCompositeSections from './WellCompositeSections'
|
||||
|
||||
const { Content } = Layout
|
||||
import '@styles/well_composite.less'
|
||||
|
||||
const properties = {
|
||||
requirements: ['OperationStat.get', 'WellComposite.get'],
|
||||
title: 'Композитная скважина',
|
||||
route: 'composite/*',
|
||||
key: 'composite',
|
||||
}
|
||||
const ClusterWells = lazy(() => import('@pages/Cluster/ClusterWells'))
|
||||
|
||||
const WellCompositeEditor = memo(() => {
|
||||
const [well] = useWell()
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/${properties.key}`, [root])
|
||||
|
||||
const [statsWells, setStatsWells] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [showTabLoader, setShowTabLoader] = useState(false)
|
||||
const [selectedIdWells, setSelectedIdWells] = useState([])
|
||||
const [selectedSections, setSelectedSections] = useState([])
|
||||
|
||||
@ -61,7 +52,7 @@ const WellCompositeEditor = memo(() => {
|
||||
const stats = arrayOrDefault(await OperationStatService.getWellsStat(selectedIdWells))
|
||||
setStatsWells(stats)
|
||||
},
|
||||
setShowTabLoader,
|
||||
setShowLoader,
|
||||
'Не удалось загрузить статистику по скважинам/секциям',
|
||||
{ actionName: 'Получение статистики по скважинам/секциям' }
|
||||
)
|
||||
@ -69,32 +60,20 @@ const WellCompositeEditor = memo(() => {
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<Row align={'middle'} justify={'space-between'} wrap={false} style={{ backgroundColor: 'white' }}>
|
||||
<Col span={18}>
|
||||
<Row align={'middle'} justify={'space-between'} wrap={false} style={{ backgroundColor: 'white', marginBottom: '15px' }}>
|
||||
<WellSelector onChange={setSelectedIdWells} value={selectedIdWells} />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<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}>
|
||||
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={ClusterWells.route} replace/>} />
|
||||
<Route index element={<Navigate to={'wells'} replace/>} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
<Route path={ClusterWells.route} element={<ClusterWells statsWells={statsWells} />} />
|
||||
<Route path={WellCompositeSections.route} element={<WellCompositeSections statsWells={statsWells} selectedSections={selectedSections} />} />
|
||||
<Route path={'wells'} element={<ClusterWells statsWells={statsWells} />} />
|
||||
<Route path={'sections'} element={<WellCompositeSections statsWells={statsWells} selectedSections={selectedSections} />} />
|
||||
</Routes>
|
||||
</LoaderPortal>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Suspense>
|
||||
</LoaderPortal>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(WellCompositeEditor, properties)
|
||||
export default wrapPrivateComponent(WellCompositeEditor, { requirements: ['OperationStat.get', 'WellComposite.get'] })
|
23
src/pages/Well/Analytics/index.jsx
Normal file
23
src/pages/Well/Analytics/index.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
|
||||
const Analytics = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/analytics`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Outlet />
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(Analytics, {
|
||||
requirements: [],
|
||||
title: 'Аналитика',
|
||||
route: 'analytics/*',
|
||||
key: 'analytics',
|
||||
})
|
3
src/pages/Documents/DocumentsTemplate.jsx → src/pages/Well/Documents/DocumentsTemplate.jsx
Executable file → Normal file
3
src/pages/Documents/DocumentsTemplate.jsx → src/pages/Well/Documents/DocumentsTemplate.jsx
Executable file → Normal file
@ -94,7 +94,7 @@ export const DocumentsTemplate = ({ idCategory, well: givenWell, mimeTypes, head
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<div style={{ margin: 16, display: 'flex' }}>
|
||||
<div style={{ margin: 16, marginTop: 0, display: 'flex' }}>
|
||||
<div>
|
||||
<span>Фильтр по дате</span>
|
||||
<div>
|
||||
@ -158,6 +158,7 @@ export const DocumentsTemplate = ({ idCategory, well: givenWell, mimeTypes, head
|
||||
onRowDelete={handleFileDelete}
|
||||
rowKey={(record) => record.id}
|
||||
tableName={tableName ?? `file_${idCategory}`}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
)
|
21
src/pages/Documents/index.jsx → src/pages/Well/Documents/index.jsx
Executable file → Normal file
21
src/pages/Documents/index.jsx → src/pages/Well/Documents/index.jsx
Executable file → Normal file
@ -1,16 +1,11 @@
|
||||
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, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { getTabname, wrapPrivateComponent, NoAccessComponent, hasPermission } from '@utils'
|
||||
import { wrapPrivateComponent, NoAccessComponent, hasPermission } from '@utils'
|
||||
|
||||
import DocumentsTemplate from './DocumentsTemplate'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
const makeDocCat = (id, key, title, permissions = ['File.get']) => ({ id, key, title, permissions })
|
||||
|
||||
export const documentCategories = [
|
||||
@ -27,7 +22,6 @@ export const documentCategories = [
|
||||
]
|
||||
|
||||
const MenuDocuments = memo(() => {
|
||||
const category = getTabname()
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/document`, [root])
|
||||
|
||||
@ -35,17 +29,6 @@ const MenuDocuments = memo(() => {
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<PrivateMenu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[category]}>
|
||||
{categories.map(category => (
|
||||
<PrivateMenu.Link
|
||||
key={`${category.key}`}
|
||||
icon={<FolderOutlined/>}
|
||||
title={category.title}
|
||||
/>
|
||||
))}
|
||||
</PrivateMenu>
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<Routes>
|
||||
{categories.length > 0 && (
|
||||
<Route index element={<Navigate to={categories[0].key} replace />} />
|
||||
@ -61,8 +44,6 @@ const MenuDocuments = memo(() => {
|
||||
)} />
|
||||
))}
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
0
src/pages/DrillingProgram/CategoryAdder.jsx → src/pages/Well/DrillingProgram/CategoryAdder.jsx
Executable file → Normal file
0
src/pages/DrillingProgram/CategoryAdder.jsx → src/pages/Well/DrillingProgram/CategoryAdder.jsx
Executable file → Normal file
2
src/pages/DrillingProgram/CategoryEditor.jsx → src/pages/Well/DrillingProgram/CategoryEditor.jsx
Executable file → Normal file
2
src/pages/DrillingProgram/CategoryEditor.jsx → src/pages/Well/DrillingProgram/CategoryEditor.jsx
Executable file → Normal file
@ -159,7 +159,7 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
|
||||
<Modal
|
||||
centered
|
||||
width={1000}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
footer={null}
|
||||
onCancel={onModalClosed}
|
||||
title={`Редактирование пользователей категории ${title}`}
|
2
src/pages/DrillingProgram/CategoryHistory.jsx → src/pages/Well/DrillingProgram/CategoryHistory.jsx
Executable file → Normal file
2
src/pages/DrillingProgram/CategoryHistory.jsx → src/pages/Well/DrillingProgram/CategoryHistory.jsx
Executable file → Normal file
@ -94,7 +94,7 @@ export const CategoryHistory = ({ idCategory, visible, onClose }) => {
|
||||
title={'История категории'}
|
||||
width={1200}
|
||||
centered
|
||||
visible={!!visible}
|
||||
open={!!visible}
|
||||
onCancel={onClose}
|
||||
footer={(
|
||||
<Button onClick={onClose}>Закрыть</Button>
|
0
src/pages/DrillingProgram/CategoryRender.jsx → src/pages/Well/DrillingProgram/CategoryRender.jsx
Executable file → Normal file
0
src/pages/DrillingProgram/CategoryRender.jsx → src/pages/Well/DrillingProgram/CategoryRender.jsx
Executable file → Normal file
0
src/pages/DrillingProgram/MarksCard.jsx → src/pages/Well/DrillingProgram/MarksCard.jsx
Executable file → Normal file
0
src/pages/DrillingProgram/MarksCard.jsx → src/pages/Well/DrillingProgram/MarksCard.jsx
Executable file → Normal file
0
src/pages/DrillingProgram/index.jsx → src/pages/Well/DrillingProgram/index.jsx
Executable file → Normal file
0
src/pages/DrillingProgram/index.jsx → src/pages/Well/DrillingProgram/index.jsx
Executable file → Normal file
2
src/pages/Measure/InclinometryTable.jsx → src/pages/Well/Measure/InclinometryTable.jsx
Executable file → Normal file
2
src/pages/Measure/InclinometryTable.jsx → src/pages/Well/Measure/InclinometryTable.jsx
Executable file → Normal file
@ -35,7 +35,7 @@ export const InclinometryTable = memo(({ group, visible, onClose }) => {
|
||||
<Modal
|
||||
title={group?.title}
|
||||
centered
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
width={1900}
|
||||
footer={null}
|
0
src/pages/Measure/MeasureTable.jsx → src/pages/Well/Measure/MeasureTable.jsx
Executable file → Normal file
0
src/pages/Measure/MeasureTable.jsx → src/pages/Well/Measure/MeasureTable.jsx
Executable file → Normal file
0
src/pages/Measure/View.jsx → src/pages/Well/Measure/View.jsx
Executable file → Normal file
0
src/pages/Measure/View.jsx → src/pages/Well/Measure/View.jsx
Executable file → Normal file
0
src/pages/Measure/drillingFluidData.js → src/pages/Well/Measure/drillingFluidData.js
Executable file → Normal file
0
src/pages/Measure/drillingFluidData.js → src/pages/Well/Measure/drillingFluidData.js
Executable file → Normal file
0
src/pages/Measure/index.jsx → src/pages/Well/Measure/index.jsx
Executable file → Normal file
0
src/pages/Measure/index.jsx → src/pages/Well/Measure/index.jsx
Executable file → Normal file
0
src/pages/Measure/mudDiagramData.js → src/pages/Well/Measure/mudDiagramData.js
Executable file → Normal file
0
src/pages/Measure/mudDiagramData.js → src/pages/Well/Measure/mudDiagramData.js
Executable file → Normal file
0
src/pages/Measure/nnbData.js → src/pages/Well/Measure/nnbData.js
Executable file → Normal file
0
src/pages/Measure/nnbData.js → src/pages/Well/Measure/nnbData.js
Executable file → Normal file
75
src/pages/Well/NavigationMenu.jsx
Normal file
75
src/pages/Well/NavigationMenu.jsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
AlertOutlined,
|
||||
BarChartOutlined,
|
||||
BuildOutlined,
|
||||
ControlOutlined,
|
||||
DatabaseOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
ExperimentOutlined,
|
||||
FilePdfOutlined,
|
||||
FolderOutlined,
|
||||
FundViewOutlined,
|
||||
LineChartOutlined,
|
||||
TableOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
import { makeItem, PrivateWellMenu } from '@components/PrivateWellMenu'
|
||||
|
||||
export const menuItems = [
|
||||
makeItem('Телеметрия', 'telemetry', [], <FundViewOutlined />, [
|
||||
makeItem('Мониторинг', 'telemetry', [], <FundViewOutlined />),
|
||||
makeItem('Сообщения', 'messages', [], <AlertOutlined />),
|
||||
makeItem('Архив', 'archive', [], <DatabaseOutlined />),
|
||||
makeItem('ННБ', 'dashboard_nnb', [], <FolderOutlined />),
|
||||
makeItem('Операции', 'operations', [], <FolderOutlined />),
|
||||
makeItem('Наработка', 'operation_time', [], <FolderOutlined />),
|
||||
]),
|
||||
makeItem('Рапорта', 'reports', [], <FilePdfOutlined />, [
|
||||
makeItem('Диаграмма', 'diagram_report', [], <FilePdfOutlined />),
|
||||
makeItem('Суточный рапорт', 'daily_report', [], <FolderOutlined />),
|
||||
]),
|
||||
makeItem('Аналитика', 'analytics', [], <DeploymentUnitOutlined />, [
|
||||
makeItem('Композитная скважина', 'composite', [], <FolderOutlined />, [
|
||||
makeItem('Статистика по скважинам', 'wells', [], <FolderOutlined />),
|
||||
makeItem('Статистика по секциям', 'sections', [], <FolderOutlined />),
|
||||
]),
|
||||
makeItem('Оценка по ЦБ', 'statistics', [], <FolderOutlined />),
|
||||
]),
|
||||
makeItem('Операции по скважине', 'operations', [], <FolderOutlined />, [
|
||||
makeItem('TVD', 'tvd', [], <LineChartOutlined />),
|
||||
makeItem('Секции', 'sections', [], <BuildOutlined />),
|
||||
makeItem('План', 'plan', [], <TableOutlined />),
|
||||
makeItem('Факт', 'fact', [], <TableOutlined />),
|
||||
makeItem('РТК', 'drillProcessFlow', [], <BarChartOutlined />),
|
||||
makeItem('Режимы', 'params', [], <ControlOutlined />),
|
||||
]),
|
||||
makeItem('Документы', 'document', [], <FolderOutlined />, [
|
||||
makeItem('Растворный сервис', 'fluidService', [], <FolderOutlined />),
|
||||
makeItem('Цементирование', 'cementing', [], <FolderOutlined />),
|
||||
makeItem('ННБ', 'nnb', [], <FolderOutlined />),
|
||||
makeItem('ГТИ', 'gti', [], <FolderOutlined />),
|
||||
makeItem('Документы по скважине', 'documentsForWell', [], <FolderOutlined />),
|
||||
makeItem('Супервайзер', 'supervisor', [], <FolderOutlined />),
|
||||
makeItem('Мастер', 'master', [], <FolderOutlined />),
|
||||
makeItem('Долотный сервис', 'toolService', [], <FolderOutlined />),
|
||||
makeItem('Буровой подрядчик', 'drillService', [], <FolderOutlined />),
|
||||
makeItem('Сервис по заканчиванию скважины', 'closingService', [], <FolderOutlined />),
|
||||
]),
|
||||
makeItem('Измерения', 'measure', [], <ExperimentOutlined />),
|
||||
makeItem('Программа бурения', 'drillingProgram', [], <FolderOutlined />),
|
||||
makeItem('Дело скважины', 'well_case', [], <FolderOutlined />),
|
||||
]
|
||||
|
||||
export const NavigationMenu = memo((props) => (
|
||||
<PrivateWellMenu
|
||||
{...props}
|
||||
items={menuItems}
|
||||
rootPath={'/well/{wellId}'}
|
||||
mode={'inline'}
|
||||
theme={'dark'}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
/>
|
||||
))
|
||||
|
||||
export default NavigationMenu
|
@ -374,7 +374,7 @@ export const ReportEditor = memo(({ visible, data, onDone, onCancel, checkIsDate
|
||||
<Modal
|
||||
centered
|
||||
width={1200}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
okText={'Сохранить'}
|
||||
title={data ? (
|
@ -4,9 +4,9 @@ import { FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||
|
||||
import { useWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { Table, makeDateSorter, makeNumericSorter } from '@components/Table'
|
||||
import { Table, makeNumericSorter, makeColumn, makeDateColumn } from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync, downloadFile } from '@components/factory'
|
||||
import { formatDate, periodToString } from '@utils'
|
||||
import { periodToString } from '@utils'
|
||||
import { ReportService } from '@api'
|
||||
|
||||
const imgPaths = {
|
||||
@ -15,10 +15,7 @@ const imgPaths = {
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Название',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
makeColumn('Название', 'name', {
|
||||
render: (name, report) => (
|
||||
<Button
|
||||
type={'link'}
|
||||
@ -29,31 +26,14 @@ const columns = [
|
||||
{name}
|
||||
</Button>
|
||||
),
|
||||
}, {
|
||||
title: <Tooltip title={'Дата формирования'}>Сформирован</Tooltip>,
|
||||
dataIndex: 'date',
|
||||
key: 'date',
|
||||
sorter: makeDateSorter('date'),
|
||||
render: (date) => formatDate(date),
|
||||
}, {
|
||||
title: <Tooltip title={'Дата начала периода рапорта'}>С</Tooltip>,
|
||||
dataIndex: 'begin',
|
||||
key: 'begin',
|
||||
sorter: makeDateSorter('begin'),
|
||||
render: (date) => formatDate(date),
|
||||
}, {
|
||||
title: <Tooltip title={'Дата окончания периода рапорта'}>По</Tooltip>,
|
||||
dataIndex: 'end',
|
||||
key: 'end',
|
||||
sorter: makeDateSorter('end'),
|
||||
render: (date) => formatDate(date),
|
||||
}, {
|
||||
title: <Tooltip title={'шаг сетки графиков'}>шаг, сек</Tooltip>,
|
||||
dataIndex: 'step',
|
||||
key: 'step',
|
||||
}),
|
||||
makeDateColumn(<Tooltip title={'Дата формирования'}>Сформирован</Tooltip>, 'date'),
|
||||
makeDateColumn(<Tooltip title={'Дата начала периода рапорта'}>С</Tooltip>, 'begin'),
|
||||
makeDateColumn(<Tooltip title={'Дата окончания периода рапорта'}>По</Tooltip>, 'end'),
|
||||
makeColumn(<Tooltip title={'шаг сетки графиков'}>шаг, сек</Tooltip>, 'step', {
|
||||
sorter: makeNumericSorter('step'),
|
||||
render: step => periodToString(step),
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
export const Reports = memo(() => {
|
||||
@ -84,6 +64,7 @@ export const Reports = memo(() => {
|
||||
dataSource={reports}
|
||||
pagination={{ pageSize: 13 }}
|
||||
tableName={'reports'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
)
|
23
src/pages/Well/Reports/index.jsx
Normal file
23
src/pages/Well/Reports/index.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
|
||||
const Reports = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/reports`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Outlet />
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(Reports, {
|
||||
requirements: [],
|
||||
title: 'Рапорта',
|
||||
route: 'reports/*',
|
||||
key: 'reports',
|
||||
})
|
0
src/pages/Telemetry/Archive/index.jsx → src/pages/Well/Telemetry/Archive/index.jsx
Executable file → Normal file
0
src/pages/Telemetry/Archive/index.jsx → src/pages/Well/Telemetry/Archive/index.jsx
Executable file → Normal file
4
src/pages/Telemetry/Messages.jsx → src/pages/Well/Telemetry/Messages.jsx
Executable file → Normal file
4
src/pages/Telemetry/Messages.jsx → src/pages/Well/Telemetry/Messages.jsx
Executable file → Normal file
@ -33,7 +33,8 @@ export const makeMessageColumns = (idWell) => [
|
||||
<Tooltip title={'Нажмите для перехода в архив'}>
|
||||
<Link
|
||||
style={{ color: 'inherit'}}
|
||||
to={`/well/${idWell}/telemetry/archive?range=1800&start=${moment(item?.date).subtract(3, 'minute').local().toISOString()}`}>
|
||||
to={`/well/${idWell}/telemetry/archive?range=1800&start=${moment(item?.date).subtract(3, 'minute').local().toISOString()}`}
|
||||
>
|
||||
<LinkOutlined />
|
||||
|
||||
{depth.toFixed(2)}
|
||||
@ -139,6 +140,7 @@ const Messages = memo(() => {
|
||||
}}
|
||||
rowKey={(record) => record.id}
|
||||
tableName={'messages'}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
</>
|
@ -53,7 +53,7 @@ export const DrillerList = memo(({ loading, drillers, onChange }) => {
|
||||
centered
|
||||
width={500}
|
||||
footer={null}
|
||||
visible={showModal}
|
||||
open={showModal}
|
||||
onCancel={onModalCancel}
|
||||
title={'Список бурильщиков'}
|
||||
>
|
@ -100,7 +100,7 @@ export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
centered
|
||||
width={1600}
|
||||
footer={null}
|
||||
visible={modalVisible}
|
||||
open={modalVisible}
|
||||
onCancel={onModalCancel}
|
||||
title={'Настройка бурильщиков и расписаний'}
|
||||
>
|
@ -42,7 +42,7 @@ export const OperationsTable = memo(({ data, height, ...other }) => (
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
tableName={'well_telemetry_detected_operations'}
|
||||
scroll={{ y: height ?? '70vh', scrollToFirstRowOnChange: true }}
|
||||
scroll={{ x: true, y: height ?? '70vh', scrollToFirstRowOnChange: true }}
|
||||
/>
|
||||
</div>
|
||||
))
|
@ -96,7 +96,7 @@ export const TargetEditor = memo(({ loading, onChange }) => {
|
||||
centered
|
||||
width={1000}
|
||||
footer={null}
|
||||
visible={showModal}
|
||||
open={showModal}
|
||||
onCancel={onModalCancel}
|
||||
title={'Цели бурения'}
|
||||
>
|
@ -1,11 +1,12 @@
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Empty, InputNumber, Select } from 'antd'
|
||||
import { InputNumber, Select } from 'antd'
|
||||
import moment from 'moment'
|
||||
|
||||
import { useWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { DateRangeWrapper } from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { unique } from '@utils/filters'
|
||||
import { getPermissions, arrayOrDefault, range, wrapPrivateComponent, pretify } from '@utils'
|
||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||
|
||||
@ -16,7 +17,6 @@ import OperationsChart from './OperationsChart'
|
||||
import OperationsTable from './OperationsTable'
|
||||
|
||||
import '@styles/detected_operations.less'
|
||||
import { unique } from '@asb/utils/filters'
|
||||
|
||||
const Operations = memo(() => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@ -161,11 +161,4 @@ const Operations = memo(() => {
|
||||
)
|
||||
})
|
||||
|
||||
export default wrapPrivateComponent(Operations, {
|
||||
requirements: [
|
||||
'DetectedOperation.get',
|
||||
'TelemetryDataSaub.get',
|
||||
],
|
||||
title: 'Операции',
|
||||
route: 'operations',
|
||||
})
|
||||
export default wrapPrivateComponent(Operations, { requirements: ['DetectedOperation.get', 'TelemetryDataSaub.get'] })
|
0
src/pages/Telemetry/TelemetryView/ActiveMessagesOnline.jsx → src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx
Executable file → Normal file
0
src/pages/Telemetry/TelemetryView/ActiveMessagesOnline.jsx → src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx
Executable file → Normal file
0
src/pages/Telemetry/TelemetryView/CustomColumn.jsx → src/pages/Well/Telemetry/TelemetryView/CustomColumn.jsx
Executable file → Normal file
0
src/pages/Telemetry/TelemetryView/CustomColumn.jsx → src/pages/Well/Telemetry/TelemetryView/CustomColumn.jsx
Executable file → Normal file
0
src/pages/Telemetry/TelemetryView/ModeDisplay.jsx → src/pages/Well/Telemetry/TelemetryView/ModeDisplay.jsx
Executable file → Normal file
0
src/pages/Telemetry/TelemetryView/ModeDisplay.jsx → src/pages/Well/Telemetry/TelemetryView/ModeDisplay.jsx
Executable file → Normal file
0
src/pages/Telemetry/TelemetryView/RigMnemo.jsx → src/pages/Well/Telemetry/TelemetryView/RigMnemo.jsx
Executable file → Normal file
0
src/pages/Telemetry/TelemetryView/RigMnemo.jsx → src/pages/Well/Telemetry/TelemetryView/RigMnemo.jsx
Executable file → Normal file
@ -80,7 +80,7 @@ export const SetpointSender = memo(({ onClose, visible, setpointNames }) => {
|
||||
<Modal
|
||||
width={800}
|
||||
title={'Рекомендовать уставки'}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
onOk={onModalOk}
|
||||
okText={'Отправить'}
|
@ -38,7 +38,7 @@ export const SetpointViewer = memo(({ setpoint, visible, onClose, setpointNames
|
||||
width={800}
|
||||
title={`Уставка от ${date}`}
|
||||
onCancel={onClose}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
footer={null}
|
||||
>
|
||||
<Grid>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user