forked from ddrilling/asb_cloud_front
* Переработан LayoutPortal
* Переработан профиль пользователя * Переработана система организации ссылок меню * Новый LayoutPortal добавлен на все страницы * Изменён редирект со страницы загрузки файла
This commit is contained in:
parent
60118f9327
commit
bd8962df26
@ -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'
|
110
src/components/LayoutPortal.tsx
Normal file
110
src/components/LayoutPortal.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { Button, Layout, LayoutProps, Menu, SiderProps } from 'antd'
|
||||
import { HTMLProps, Key, memo, ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems'
|
||||
import { Link } from 'react-router-dom'
|
||||
import {
|
||||
ApartmentOutlined,
|
||||
CodeOutlined,
|
||||
HomeOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
import PageHeader from '@components/PageHeader'
|
||||
import { UserMenu, UserMenuProps } from '@components/UserMenu'
|
||||
import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector'
|
||||
import { isURLAvailable, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import '@styles/layout.less'
|
||||
|
||||
const { Content, Sider } = Layout
|
||||
|
||||
export type LayoutPortalProps = HTMLProps<HTMLDivElement> & {
|
||||
title?: ReactNode
|
||||
noSheet?: boolean
|
||||
showSelector?: boolean
|
||||
selectorProps?: WellTreeSelectorProps
|
||||
sider?: boolean | JSX.Element
|
||||
siderProps?: SiderProps & { userMenuProps?: UserMenuProps }
|
||||
isAdmin?: boolean
|
||||
}
|
||||
|
||||
const makeItem = (title: string, key: Key, icon: JSX.Element, label?: ReactNode, onClick?: () => void) => ({ icon, key, title, label: label ?? title, onClick })
|
||||
|
||||
const _LayoutPortal = memo<LayoutPortalProps>(({ isAdmin, title, noSheet, showSelector, selectorProps, sider, siderProps, ...props }) => {
|
||||
const [menuCollapsed, setMenuCollapsed] = useState<boolean>(true)
|
||||
const [wellsTreeOpen, setWellsTreeOpen] = useState<boolean>(false)
|
||||
const [userMenuOpen, setUserMenuOpen] = useState<boolean>(false)
|
||||
const [currentWell, setCurrentWell] = useState<string>('')
|
||||
|
||||
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} collapsed={menuCollapsed} trigger={null} collapsible className={`menu-sider ${siderProps?.className || ''}`}>
|
||||
<div className={'sider-content'}>
|
||||
<button className={'sider-toogle'} onClick={() => setMenuCollapsed((prev) => !prev)}>
|
||||
{menuCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</button>
|
||||
<div className={'scrollable hide-slider'}>
|
||||
{sider}
|
||||
</div>
|
||||
<Menu
|
||||
mode={'inline'}
|
||||
items={menuItems}
|
||||
inlineCollapsed
|
||||
theme={'dark'}
|
||||
selectable={false}
|
||||
/>
|
||||
<UserMenu
|
||||
open={userMenuOpen}
|
||||
onClose={() => setUserMenuOpen(false)}
|
||||
isAdmin={isAdmin}
|
||||
{...siderProps?.userMenuProps}
|
||||
/>
|
||||
</div>
|
||||
</Sider>
|
||||
)}
|
||||
<Layout className={'page-content'}>
|
||||
<PageHeader title={title}>
|
||||
{isAdmin ? (
|
||||
<Button size={'large'}>
|
||||
<Link to={'/'}>Вернуться на сайт</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<WellTreeSelector
|
||||
open={wellsTreeOpen}
|
||||
onClose={() => setWellsTreeOpen(false)}
|
||||
{...selectorProps}
|
||||
onChange={(well) => setCurrentWell(well ?? 'Выберите месторождение')}
|
||||
/>
|
||||
<Button onClick={() => setWellsTreeOpen((prev) => !prev)}>{currentWell}</Button>
|
||||
</>
|
||||
)}
|
||||
</PageHeader>
|
||||
<Content>
|
||||
<div {...props} className={`${noSheet ? '' : 'site-layout-background sheet'} ${props.className}`} />
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
})
|
||||
|
||||
export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, {
|
||||
requirements: ['Deposit.get'],
|
||||
})
|
||||
|
||||
export default LayoutPortal
|
@ -4,27 +4,24 @@ 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'
|
||||
|
||||
import '@styles/layout.less'
|
||||
|
||||
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 const PageHeader: React.FC<PageHeaderProps> = memo(({ title = 'Мониторинг', children, ...other }) => (
|
||||
<Layout.Header className={'header'} {...other}>
|
||||
<Link to={'/'} style={{ height: headerHeight }}>
|
||||
<Logo />
|
||||
</Link>
|
||||
<h1 className={'title'}>{title}</h1>
|
||||
{children}
|
||||
</Layout.Header>
|
||||
))
|
||||
|
||||
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'
|
97
src/components/PrivateWellMenu.tsx
Normal file
97
src/components/PrivateWellMenu.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
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 { useWell } from '@asb/context'
|
||||
import { hasPermission, Permission } from '@utils'
|
||||
|
||||
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 = MenuProps & {
|
||||
items: PrivateWellMenuItem[]
|
||||
rootPath?: string
|
||||
}
|
||||
|
||||
export const PrivateWellMenu = memo<PrivateWellMenuProps>(({ items, rootPath = '/', ...other }) => {
|
||||
const [well] = useWell()
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
const menuItems = useMemo(() => makeItemList(items, rootPath, well.id), [items, rootPath, well.id])
|
||||
|
||||
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} />
|
||||
})
|
@ -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
|
||||
{...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>
|
||||
<Drawer
|
||||
closable
|
||||
placement={'left'}
|
||||
className={'user-menu'}
|
||||
title={'Профиль пользователя'}
|
||||
{...other}
|
||||
>
|
||||
<div className={'profile-links'}>
|
||||
{isAdmin ? (
|
||||
<Button onClick={() => navigateTo('/')}>Вернуться на сайт</Button>
|
||||
) : isURLAvailable('/admin') && (
|
||||
<Button onClick={() => navigateTo('/admin')}>Панель администратора</Button>
|
||||
)}
|
||||
>
|
||||
<Button icon={<UserOutlined/>}>{getUserLogin()}</Button>
|
||||
</Dropdown>
|
||||
<ChangePassword
|
||||
visible={isModalVisible}
|
||||
onOk={onChangePasswordOk}
|
||||
onCancel={() => setIsModalVisible(false)}
|
||||
/>
|
||||
</>
|
||||
<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>
|
||||
)
|
||||
})
|
||||
|
@ -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,26 +169,22 @@ 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>
|
||||
<Skeleton active loading={showLoader}>
|
||||
<Tree
|
||||
{...other}
|
||||
showIcon
|
||||
selectedKeys={selected}
|
||||
treeData={wellsTree}
|
||||
onSelect={onSelect}
|
||||
onExpand={setExpanded}
|
||||
expandedKeys={expanded}
|
||||
/>
|
||||
</Skeleton>
|
||||
</Drawer>
|
||||
</>
|
||||
<Drawer open={open} mask={false} onClose={onClose} title={'Список скважин'}>
|
||||
<Skeleton active loading={showLoader}>
|
||||
<Tree
|
||||
{...other}
|
||||
showIcon
|
||||
selectedKeys={selected}
|
||||
treeData={wellsTree}
|
||||
onSelect={onSelect}
|
||||
onExpand={setExpanded}
|
||||
expandedKeys={expanded}
|
||||
/>
|
||||
</Skeleton>
|
||||
</Drawer>
|
||||
)
|
||||
})
|
||||
|
||||
|
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'
|
||||
|
||||
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 />),
|
||||
makeItem('API', '/swagger/index.html', [], <ApiOutlined />),
|
||||
]
|
||||
|
||||
export const AdminNavigationMenu = memo((props) => (
|
||||
<PrivateWellMenu
|
||||
{...props}
|
||||
items={menuItems}
|
||||
rootPath={'/admin'}
|
||||
inlineCollapsed={true}
|
||||
selectable={false}
|
||||
mode={'inline'}
|
||||
theme={'dark'}
|
||||
/>
|
||||
))
|
||||
|
||||
export default AdminNavigationMenu
|
@ -1,12 +1,11 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Layout } from 'antd'
|
||||
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { AdminLayoutPortal } from '@components/Layout'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { LayoutPortal } from '@components/LayoutPortal'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import AdminNavigationMenu from './AdminNavigationMenu'
|
||||
import ClusterController from './ClusterController'
|
||||
import CompanyController from './CompanyController'
|
||||
import DepositController from './DepositController'
|
||||
@ -24,39 +23,22 @@ const AdminPanel = memo(() => {
|
||||
|
||||
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 path={'*'} element={<NoAccessComponent />} />
|
||||
<Route path={DepositController.route} element={<DepositController />} />
|
||||
<Route path={ClusterController.route} element={<ClusterController />} />
|
||||
<Route path={WellController.route} element={<WellController />} />
|
||||
<Route path={UserController.route} element={<UserController />} />
|
||||
<Route path={CompanyController.route} element={<CompanyController />} />
|
||||
<Route path={CompanyTypeController.route} element={<CompanyTypeController />} />
|
||||
<Route path={RoleController.route} element={<RoleController />} />
|
||||
<Route path={PermissionController.route} element={<PermissionController />} />
|
||||
<Route path={Telemetry.route} element={<Telemetry />} />
|
||||
<Route path={VisitLog.route} element={<VisitLog />} />
|
||||
</Routes>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</AdminLayoutPortal>
|
||||
<LayoutPortal isAdmin title={'Администраторская панель'} sider={<AdminNavigationMenu />}>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={VisitLog.route} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
<Route path={DepositController.route} element={<DepositController />} />
|
||||
<Route path={ClusterController.route} element={<ClusterController />} />
|
||||
<Route path={WellController.route} element={<WellController />} />
|
||||
<Route path={UserController.route} element={<UserController />} />
|
||||
<Route path={CompanyController.route} element={<CompanyController />} />
|
||||
<Route path={CompanyTypeController.route} element={<CompanyTypeController />} />
|
||||
<Route path={RoleController.route} element={<RoleController />} />
|
||||
<Route path={PermissionController.route} element={<PermissionController />} />
|
||||
<Route path={Telemetry.route} element={<Telemetry />} />
|
||||
<Route path={VisitLog.route} element={<VisitLog />} />
|
||||
</Routes>
|
||||
</LayoutPortal>
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { LayoutPortal } from '@components/Layout'
|
||||
import { LayoutPortal } from '@components/LayoutPortal'
|
||||
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 Cluster = memo(() => {
|
||||
const { idCluster } = useParams()
|
||||
@ -27,7 +27,7 @@ const Cluster = memo(() => {
|
||||
}, [idCluster])
|
||||
|
||||
return (
|
||||
<LayoutPortal title={'Анализ скважин куста'}>
|
||||
<LayoutPortal title={'Анализ скважин куста'} sider={true}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<ClusterWells statsWells={data} />
|
||||
</LoaderPortal>
|
||||
|
@ -4,7 +4,7 @@ import { Link, useLocation } from 'react-router-dom'
|
||||
import { Popover, Badge } from 'antd'
|
||||
|
||||
import { PointerIcon } from '@components/icons'
|
||||
import { LayoutPortal } from '@components/Layout'
|
||||
import { LayoutPortal } from '@components/LayoutPortal'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault, limitValue, wrapPrivateComponent } from '@utils'
|
||||
@ -53,7 +53,6 @@ const Deposit = memo(() => {
|
||||
const hasId = location.pathname.length > '/deposit/'.length
|
||||
|
||||
return {
|
||||
show: true,
|
||||
expand: hasId ? [location.pathname] : true,
|
||||
current: hasId ? location.pathname : undefined,
|
||||
}
|
||||
@ -73,9 +72,9 @@ const Deposit = memo(() => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<LayoutPortal noSheet selector={selectorProps} title={'Месторождение'}>
|
||||
<LayoutPortal noSheet showSelector selectorProps={selectorProps} title={'Месторождение'} sider={true}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<div className={'h-100vh'}>
|
||||
<div className={'h-100vh'} style={{ overflow: 'hidden' }}>
|
||||
<Map {...viewParams}>
|
||||
{depositsData.map(deposit => (
|
||||
<Overlay
|
||||
|
@ -7,8 +7,6 @@ import { downloadFile, invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
import { FileService, WellService } from '@api'
|
||||
|
||||
import AccessDenied from './AccessDenied'
|
||||
|
||||
const { Paragraph, Text } = Typography
|
||||
|
||||
export const getLinkToFile = (fileInfo) => `/file_download/${fileInfo.idWell}/${fileInfo.id}`
|
||||
@ -104,4 +102,4 @@ FileDownload.displayName = 'FileDownloadMemo'
|
||||
export default wrapPrivateComponent(FileDownload, {
|
||||
requirements: ['File.get'],
|
||||
route: 'file_download/:idWell/:idFile/*',
|
||||
}, <AccessDenied />)
|
||||
})
|
||||
|
78
src/pages/NavigationMenu.jsx
Normal file
78
src/pages/NavigationMenu.jsx
Normal file
@ -0,0 +1,78 @@
|
||||
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'
|
||||
|
||||
|
||||
|
||||
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}'}
|
||||
inlineCollapsed={true}
|
||||
mode={'inline'}
|
||||
theme={'dark'}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
/>
|
||||
))
|
||||
|
||||
export default NavigationMenu
|
@ -1,16 +1,8 @@
|
||||
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 { Navigate, Outlet, 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 { LayoutPortal } from '@components/LayoutPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
import { WellService } from '@api'
|
||||
@ -23,10 +15,10 @@ import Documents from './Documents'
|
||||
import Telemetry from './Telemetry'
|
||||
import WellOperations from './WellOperations'
|
||||
import DrillingProgram from './DrillingProgram'
|
||||
import NavigationMenu from './NavigationMenu'
|
||||
|
||||
import '@styles/index.css'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
const Well = memo(() => {
|
||||
const { idWell } = useParams()
|
||||
@ -59,40 +51,26 @@ const Well = memo(() => {
|
||||
), [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>
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<WellContext.Provider value={[well, updateWell]}>
|
||||
<LayoutPortal sider={<NavigationMenu />}>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={Telemetry.getKey()} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<Outlet />
|
||||
</LayoutPortal>
|
||||
</WellContext.Provider>
|
||||
</RootPathContext.Provider>
|
||||
)
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user