* Добавлен компонент для получения Route по умолчанию

* Доблена страница "Доступ запрещён"
* Актуализирована работа с правами
* Добавлен переключатель оси X TVD
This commit is contained in:
Александр Сироткин 2022-02-07 14:58:38 +05:00
parent 439df9401a
commit 85f98044c6
31 changed files with 740 additions and 617 deletions

View File

@ -1,11 +1,13 @@
import React from 'react' import React from 'react'
import { Role, Permission, hasPermission, isInRole } from '../../utils/permissions'
type PrivateContentProps = { import { isURLAvailable } from '@utils/permissions'
roles?: Role[] | Role
permission?: Permission export type PrivateContentProps = {
absolutePath: string
children?: React.ReactElement<any, any> children?: React.ReactElement<any, any>
} }
export const PrivateContent: React.FC<PrivateContentProps> = ({ permission, roles, children = null }) => export const PrivateContent: React.FC<PrivateContentProps> = ({ absolutePath, children = null }) =>
hasPermission(permission) && isInRole(roles) ? children : null isURLAvailable(absolutePath) ? children : null
export default PrivateContent

View File

@ -0,0 +1,17 @@
import { memo } from 'react'
import { Redirect, Route, RouteProps } from 'react-router-dom'
import { isURLAvailable } from '@utils/permissions'
export type PrivateDefaultRouteProps = RouteProps & {
urls: string[]
elseRedirect?: string
}
export const PrivateDefaultRoute = memo<PrivateDefaultRouteProps>(({ elseRedirect, urls, ...other }) => (
<Route {...other} path={'/'}>
<Redirect to={{ pathname: urls.find((url) => isURLAvailable(url)) ?? elseRedirect ?? '/access_denied' }} />
</Route>
))
export default PrivateDefaultRoute

View File

@ -1,14 +1,34 @@
import React from 'react' import { join } from 'path'
import { Menu, MenuItemProps } from 'antd' import { Menu, MenuItemProps } from 'antd'
import { Role, Permission, hasPermission, isInRole } from '../../utils/permissions' import { memo, NamedExoticComponent } from 'react'
import { isURLAvailable } from '@utils/permissions'
import { Link } from 'react-router-dom'
type PrivateMenuItemProps = MenuItemProps & { export type PrivateMenuItemProps = MenuItemProps & {
roles?: Role[] | Role root: string
permission?: Permission path: string
} }
export const PrivateMenuItem: React.FC<PrivateMenuItemProps> = ({ roles, permission, ...props }) => export type PrivateMenuLinkProps = MenuItemProps & {
hasPermission(permission) && isInRole(roles) ? <Menu.Item {...props}/> : null root?: string
path: string
title: string
}
export const PrivateMenuItemLink = memo<PrivateMenuLinkProps>(({ root = '', path, title, ...other }) => (
<PrivateMenuItem key={path} root={root} path={path} {...other}>
<Link to={join(root, path)}>{title}</Link>
</PrivateMenuItem>
))
export const PrivateMenuItem: NamedExoticComponent<PrivateMenuItemProps> & {
Link: NamedExoticComponent<PrivateMenuLinkProps>
} = Object.assign(memo<PrivateMenuItemProps>(({ root, path, ...other }) =>
isURLAvailable(join(root, path)) ? <Menu.Item key={path} {...other}/> : null
), {
Link: PrivateMenuItemLink
})
export default PrivateMenuItem export default PrivateMenuItem

View File

@ -1,30 +1,32 @@
import { FC, ReactNode } from 'react'
import { Location } from 'history' import { Location } from 'history'
import { memo, ReactNode } from 'react'
import { Redirect, Route, RouteProps } from 'react-router-dom' import { Redirect, Route, RouteProps } from 'react-router-dom'
import { join } from 'path'
import { Role, Permission, hasPermission, isInRole } from '../../utils/permissions' import { isURLAvailable } from '@utils/permissions'
import { getUserToken } from '../../utils/storage'
export type PrivateRouteProps = RouteProps & { export type PrivateRouteProps = RouteProps & {
roles: Role[] | Role root: string
permission?: Permission path: string
children?: ReactNode children?: ReactNode
redirect?: (location?: Location<unknown>) => ReactNode redirect?: (location?: Location<unknown>) => ReactNode
} }
export const defaultRedirect = (location?: Location<unknown>) => ( export const defaultRedirect = (location?: Location<unknown>) => (
<Redirect to={{ pathname: '/login', state: { from: location } }} /> <Redirect to={{ pathname: '/access_denied', state: { from: location } }} />
) )
export const PrivateRoute: FC<PrivateRouteProps> = ({ permission, roles, component, children, redirect = defaultRedirect, ...other }) => { export const PrivateRoute = memo<PrivateRouteProps>(({ root = '', path, component, children, redirect = defaultRedirect, ...other }) => {
const available = getUserToken() && (hasPermission(permission) && isInRole(roles)) const available = isURLAvailable(join(root, path))
return ( return (
<Route {...other} <Route
{...other}
path={path}
component={available ? component : undefined} component={available ? component : undefined}
render={({ location }) => available ? children : redirect(location)} render={({ location }) => available ? children : redirect(location)}
/> />
) )
} })
export default PrivateRoute export default PrivateRoute

View File

@ -1,3 +1,9 @@
export { PrivateRoute } from './PrivateRoute' export { PrivateRoute, defaultRedirect } from './PrivateRoute'
export { PrivateContent } from './PrivateContent' export { PrivateContent } from './PrivateContent'
export { PrivateMenuItem } from './PrivateMenuItem' export { PrivateMenuItem, PrivateMenuItemLink } from './PrivateMenuItem'
export { PrivateDefaultRoute } from './PrivateDefaultRoute'
export type { PrivateRouteProps } from './PrivateRoute'
export type { PrivateContentProps } from './PrivateContent'
export type { PrivateMenuItemProps, PrivateMenuLinkProps } from './PrivateMenuItem'
export type { PrivateDefaultRouteProps } from './PrivateDefaultRoute'

View File

@ -1,18 +1,16 @@
import { MouseEventHandler, useState } from 'react' import { memo, MouseEventHandler, useState } from 'react'
import { Link, useHistory } from 'react-router-dom' import { Link, useHistory } from 'react-router-dom'
import { Button, Dropdown, Menu } from 'antd' import { Button, Dropdown, DropDownProps, Menu } from 'antd'
import { UserOutlined } from '@ant-design/icons' import { UserOutlined } from '@ant-design/icons'
import { getUserLogin, removeUser } from '../utils/storage' import { getUserLogin, removeUser } from '@utils/storage'
import { PrivateMenuItem } from './Private'
import { ChangePassword } from './ChangePassword' import { ChangePassword } from './ChangePassword'
import { PrivateMenuItemLink } from './Private/PrivateMenuItem'
type UserMenuProps = { type UserMenuProps = Omit<DropDownProps, 'overlay'> & { isAdmin?: boolean }
isAdmin?: boolean
}
export const UserMenu: React.FC<UserMenuProps> = ({ isAdmin }) => { export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
const [isModalVisible, setIsModalVisible] = useState<boolean>(false) const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
const history = useHistory() const history = useHistory()
@ -30,16 +28,15 @@ export const UserMenu: React.FC<UserMenuProps> = ({ isAdmin }) => {
return ( return (
<> <>
<Dropdown <Dropdown
{...other}
placement={'bottomRight'} placement={'bottomRight'}
overlay={( overlay={(
<Menu style={{ textAlign: 'right' }}> <Menu style={{ textAlign: 'right' }}>
<PrivateMenuItem permission={'admin_panel'}>
{isAdmin ? ( {isAdmin ? (
<Link to={'/'}>Вернуться на сайт</Link> <PrivateMenuItemLink path={'/'} title={'Вернуться на сайт'}/>
) : ( ) : (
<Link to={'/admin'}>Панель администратора</Link> <PrivateMenuItemLink path={'/admin'} title={'Панель администратора'}/>
)} )}
</PrivateMenuItem>
<Menu.Item> <Menu.Item>
<Link to={'/'} onClick={onChangePasswordClick}>Сменить пароль</Link> <Link to={'/'} onClick={onChangePasswordClick}>Сменить пароль</Link>
</Menu.Item> </Menu.Item>
@ -58,4 +55,4 @@ export const UserMenu: React.FC<UserMenuProps> = ({ isAdmin }) => {
/> />
</> </>
) )
} })

View File

@ -0,0 +1,15 @@
import { memo } from 'react'
export const AccessDenied = memo(() => (
<div style={{
width: '100vw',
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}>
Доступ запрещён
</div>
))
export default AccessDenied

View File

@ -1,7 +1,5 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { import {
EditableTable, EditableTable,
makeColumn, makeColumn,
@ -9,10 +7,13 @@ import {
makeActionHandler, makeActionHandler,
makeStringSorter, makeStringSorter,
defaultPagination defaultPagination
} from '../../components/Table' } from '@components/Table'
import { AdminClusterService, AdminDepositService } from '../../services/api' import LoaderPortal from '@components/LoaderPortal'
import { arrayOrDefault } from '../../utils' import { invokeWebApiWrapperAsync } from '@components/factory'
import { min1 } from '../../utils/validationRules' import { AdminClusterService, AdminDepositService } from '@api'
import { arrayOrDefault } from '@utils'
import { min1 } from '@utils/validationRules'
import { hasPermission } from '@utils/permissions'
import { coordsFixed } from './DepositController' import { coordsFixed } from './DepositController'
@ -73,9 +74,9 @@ export const ClusterController = () => {
dataSource={clusters} dataSource={clusters}
columns={clusterColumns} columns={clusterColumns}
pagination={defaultPagination} pagination={defaultPagination}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={hasPermission('AdminCluster.edit') && makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('put', handlerProps)} onRowEdit={hasPermission('AdminCluster.edit') && makeActionHandler('put', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminCluster.delete') && makeActionHandler('delete', handlerProps)}
/> />
</LoaderPortal> </LoaderPortal>
) )

View File

@ -1,7 +1,5 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { import {
EditableTable, EditableTable,
makeColumn, makeColumn,
@ -9,10 +7,13 @@ import {
makeStringSorter, makeStringSorter,
makeSelectColumn, makeSelectColumn,
defaultPagination defaultPagination
} from '../../components/Table' } from '@components/Table'
import { AdminCompanyService, AdminCompanyTypeService } from '../../services/api' import LoaderPortal from '@components/LoaderPortal'
import { arrayOrDefault } from '../../utils' import { invokeWebApiWrapperAsync } from '@components/factory'
import { min1 } from '../../utils/validationRules' import { AdminCompanyService, AdminCompanyTypeService } from '@api'
import { arrayOrDefault } from '@utils'
import { min1 } from '@utils/validationRules'
import { hasPermission } from '@utils/permissions'
export const CompanyController = () => { export const CompanyController = () => {
@ -70,9 +71,9 @@ export const CompanyController = () => {
columns={columns} columns={columns}
dataSource={companies} dataSource={companies}
pagination={defaultPagination} pagination={defaultPagination}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={hasPermission('AdminCompany.edit') && makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('put', handlerProps)} onRowEdit={hasPermission('AdminCompany.edit') && makeActionHandler('put', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminCompany.delete') && makeActionHandler('delete', handlerProps)}
/> />
</LoaderPortal> </LoaderPortal>
) )

View File

@ -1,17 +1,18 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { import {
EditableTable, EditableTable,
makeColumn, makeColumn,
makeActionHandler, makeActionHandler,
makeStringSorter, makeStringSorter,
defaultPagination defaultPagination
} from '../../components/Table' } from '@components/Table'
import { AdminCompanyTypeService } from '../../services/api' import LoaderPortal from '@components/LoaderPortal'
import { arrayOrDefault } from '../../utils' import { invokeWebApiWrapperAsync } from '@components/factory'
import { min1 } from '../../utils/validationRules' import { AdminCompanyTypeService } from '@api'
import { arrayOrDefault } from '@utils'
import { min1 } from '@utils/validationRules'
import { hasPermission } from '@asb/utils/permissions'
const columns = [ const columns = [
makeColumn('Название', 'caption', { makeColumn('Название', 'caption', {
@ -52,9 +53,9 @@ export const CompanyTypeController = () => {
columns={columns} columns={columns}
dataSource={companyTypes} dataSource={companyTypes}
pagination={defaultPagination} pagination={defaultPagination}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={hasPermission('AdminCompanyType.edit') && makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('put', handlerProps)} onRowEdit={hasPermission('AdminCompanyType.edit') && makeActionHandler('put', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminCompanyType.delete') && makeActionHandler('delete', handlerProps)}
/> />
</LoaderPortal> </LoaderPortal>
) )

View File

@ -1,11 +1,12 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import LoaderPortal from '../../components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { EditableTable, makeColumn, makeActionHandler, defaultPagination } from '../../components/Table' import { EditableTable, makeColumn, makeActionHandler, defaultPagination } from '@components/Table'
import { AdminDepositService } from '../../services/api' import { AdminDepositService } from '@api'
import { arrayOrDefault } from '../../utils' import { arrayOrDefault } from '@utils'
import { min1 } from '../../utils/validationRules' import { min1 } from '@utils/validationRules'
import { hasPermission } from '@utils/permissions'
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-' export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
@ -45,9 +46,9 @@ export const DepositController = () => {
dataSource={deposits} dataSource={deposits}
columns={depositColumns} columns={depositColumns}
pagination={defaultPagination} pagination={defaultPagination}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={hasPermission('AdminDeposit.edit') && makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('put', handlerProps)} onRowEdit={hasPermission('AdminDeposit.edit') && makeActionHandler('put', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminDeposit.delete') && makeActionHandler('delete', handlerProps)}
/> />
</LoaderPortal> </LoaderPortal>
) )

View File

@ -1,16 +1,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { import {
EditableTable, EditableTable,
makeActionHandler, makeActionHandler,
makeColumn, makeColumn,
makeStringSorter makeStringSorter
} from '../../components/Table' } from '@components/Table'
import { AdminPermissionService } from '../../services/api' import LoaderPortal from '@components/LoaderPortal'
import { arrayOrDefault } from '../../utils' import { invokeWebApiWrapperAsync } from '@components/factory'
import { min1 } from '../../utils/validationRules' import { AdminPermissionService } from '@api'
import { arrayOrDefault } from '@utils'
import { min1 } from '@utils/validationRules'
import { hasPermission } from '@utils/permissions'
const columns = [ const columns = [
makeColumn('Название', 'name', { makeColumn('Название', 'name', {
@ -55,9 +56,9 @@ export const PermissionController = () => {
columns={columns} columns={columns}
dataSource={permissions} dataSource={permissions}
pagination={{ showSizeChanger: true }} pagination={{ showSizeChanger: true }}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={hasPermission('AdminPermission.edit') && makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('put', handlerProps)} onRowEdit={hasPermission('AdminPermission.edit') && makeActionHandler('put', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminPermission.delete') && makeActionHandler('delete', handlerProps)}
/> />
</LoaderPortal> </LoaderPortal>
) )

View File

@ -1,12 +1,13 @@
import { memo, useEffect, useState } from 'react' import { memo, useEffect, useState } from 'react'
import LoaderPortal from '../../components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import { PermissionView, RoleView } from '../../components/views' import { PermissionView, RoleView } from '@components/views'
import { invokeWebApiWrapperAsync } from '../../components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { EditableTable, makeActionHandler, makeColumn, makeTagColumn } from '../../components/Table' import { EditableTable, makeActionHandler, makeColumn, makeTagColumn } from '@components/Table'
import { AdminPermissionService, AdminUserRoleService } from '../../services/api' import { AdminPermissionService, AdminUserRoleService } from '@api'
import { arrayOrDefault } from '../../utils' import { arrayOrDefault } from '@utils'
import { min1 } from '../../utils/validationRules' import { min1 } from '@utils/validationRules'
import { hasPermission } from '@utils/permissions'
export const RoleController = memo(() => { export const RoleController = memo(() => {
const [permissions, setPermissions] = useState([]) const [permissions, setPermissions] = useState([])
@ -62,9 +63,9 @@ export const RoleController = memo(() => {
size={'small'} size={'small'}
columns={columns} columns={columns}
dataSource={roles} dataSource={roles}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={hasPermission('AdminUserRole.edit') && makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('put', handlerProps)} onRowEdit={hasPermission('AdminUserRole.edit') && makeActionHandler('put', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminUserRole.delete') && makeActionHandler('delete', handlerProps)}
/> />
</LoaderPortal> </LoaderPortal>
) )

View File

@ -2,10 +2,6 @@ import { Button, Tag } from 'antd'
import { UserSwitchOutlined } from '@ant-design/icons' import { UserSwitchOutlined } from '@ant-design/icons'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { RoleView } from '../../../components/views'
import LoaderPortal from '../../../components/LoaderPortal'
import { ChangePassword } from '../../../components/ChangePassword'
import { invokeWebApiWrapperAsync } from '../../../components/factory'
import { import {
EditableTable, EditableTable,
makeColumn, makeColumn,
@ -14,14 +10,19 @@ import {
makeStringSorter, makeStringSorter,
makeNumericSorter, makeNumericSorter,
defaultPagination defaultPagination
} from '../../../components/Table' } from '@components/Table'
import { AdminCompanyService, AdminUserRoleService, AdminUserService } from '../../../services/api' import { RoleView } from '@components/views'
import { createLoginRules, nameRules, phoneRules, emailRules } from '../../../utils/validationRules' import LoaderPortal from '@components/LoaderPortal'
import { arrayOrDefault } from '../../../utils' import { ChangePassword } from '@components/ChangePassword'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { AdminCompanyService, AdminUserRoleService, AdminUserService } from '@api'
import { createLoginRules, nameRules, phoneRules, emailRules } from '@utils/validationRules'
import { makeTextOnFilter, makeTextFilters } from '@utils/table'
import { hasPermission } from '@utils/permissions'
import { arrayOrDefault } from '@utils'
import RoleTag from './RoleTag' import RoleTag from './RoleTag'
import { makeTextOnFilter, makeTextFilters } from '../../../utils/table'
export const UserController = () => { export const UserController = () => {
const [users, setUsers] = useState([]) const [users, setUsers] = useState([])
@ -153,9 +154,9 @@ export const UserController = () => {
bordered bordered
columns={columns} columns={columns}
dataSource={users} dataSource={users}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={hasPermission('AdminUser.edit') && makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('put', handlerProps)} onRowEdit={hasPermission('AdminUser.edit') && makeActionHandler('put', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminUser.delete') && makeActionHandler('delete', handlerProps)}
additionalButtons={additionalButtons} additionalButtons={additionalButtons}
buttonsWidth={120} buttonsWidth={120}
pagination={defaultPagination} pagination={defaultPagination}

View File

@ -3,15 +3,10 @@ import { Select } from 'antd'
import { getTelemetryLabel } from '@components/views' import { getTelemetryLabel } from '@components/views'
export const TelemetrySelect = memo(({ telemetry, value, onChange }) => { export const TelemetrySelect = memo(({ telemetry, value, onChange }) => (
const onSelectChange = (id) => {
onChange?.(telemetry.find((row) => row.id === id))
}
return (
<Select <Select
value={value?.id} value={value?.id}
onChange={onSelectChange} onChange={(id) => onChange?.(telemetry.find((row) => row.id === id))}
dropdownClassName={'telemetry_select'} dropdownClassName={'telemetry_select'}
> >
{telemetry.map((row, i) => ( {telemetry.map((row, i) => (
@ -22,7 +17,6 @@ export const TelemetrySelect = memo(({ telemetry, value, onChange }) => {
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
) ))
})
export default TelemetrySelect export default TelemetrySelect

View File

@ -2,9 +2,12 @@ import { useEffect, useState } from 'react'
import { Button } from 'antd' import { Button } from 'antd'
import { CopyOutlined } from '@ant-design/icons' import { CopyOutlined } from '@ant-design/icons'
import LoaderPortal from '../../../components/LoaderPortal' import {
import { invokeWebApiWrapperAsync } from '../../../components/factory' AdminClusterService,
import { TelemetryView, CompanyView } from '../../../components/views' AdminCompanyService,
AdminTelemetryService,
AdminWellService,
} from '@api'
import { import {
EditableTable, EditableTable,
makeColumn, makeColumn,
@ -14,19 +17,17 @@ import {
makeNumericSorter, makeNumericSorter,
makeTagColumn, makeTagColumn,
defaultPagination, defaultPagination,
} from '../../../components/Table' } from '@components/Table'
import { import LoaderPortal from '@components/LoaderPortal'
AdminClusterService, import { invokeWebApiWrapperAsync } from '@components/factory'
AdminCompanyService, import { TelemetryView, CompanyView } from '@components/views'
AdminTelemetryService, import { arrayOrDefault } from '@utils'
AdminWellService,
} from '../../../services/api'
import { arrayOrDefault } from '../../../utils'
import { coordsFixed } from '../DepositController' import { coordsFixed } from '../DepositController'
import TelemetrySelect from './TelemetrySelect' import TelemetrySelect from './TelemetrySelect'
import '../../../styles/admin.css' import '@styles/admin.css'
import { hasPermission } from '@asb/utils/permissions'
const wellTypes = [ const wellTypes = [
{ value: 1, label: 'Наклонно-направленная' }, { value: 1, label: 'Наклонно-направленная' },
@ -124,9 +125,9 @@ export const WellController = () => {
columns={columns} columns={columns}
dataSource={wells} dataSource={wells}
pagination={defaultPagination} pagination={defaultPagination}
onRowAdd={makeActionHandler('insert', handlerProps, recordParser)} onRowAdd={hasPermission('AdminWell.edit') && makeActionHandler('insert', handlerProps, recordParser)}
onRowEdit={makeActionHandler('put', handlerProps, recordParser)} onRowEdit={hasPermission('AdminWell.edit') && makeActionHandler('put', handlerProps, recordParser)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('AdminWell.delete') && makeActionHandler('delete', handlerProps)}
//additionalButtons={addititonalButtons} //additionalButtons={addititonalButtons}
buttonsWidth={95} buttonsWidth={95}
/> />

View File

@ -1,76 +1,68 @@
import { Layout, Menu } from 'antd' import { Layout, Menu } from 'antd'
import { lazy, Suspense } from 'react' import { lazy, Suspense } from 'react'
import { Switch, Link, useParams, Redirect, Route } from 'react-router-dom' import { Switch, useParams } from 'react-router-dom'
import { PrivateMenuItem, PrivateRoute } from '../../components/Private'
import { SuspenseFallback } from '../SuspenseFallback'
const ClusterController = lazy(() => import('./ClusterController')) import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private'
const CompanyController = lazy(() => import('./CompanyController'))
const DepositController = lazy(() => import('./DepositController')) import { SuspenseFallback } from '@pages/SuspenseFallback'
const UserController = lazy(() => import('./UserController'))
const WellController = lazy(() => import('./WellController')) const ClusterController = lazy(() => import( './ClusterController'))
const RoleController = lazy(() => import('./RoleController')) 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 CompanyTypeController = lazy(() => import('./CompanyTypeController'))
const PermissionController = lazy(() => import('./PermissionController')) const PermissionController = lazy(() => import( './PermissionController'))
const TelemetryController = lazy(() => import('./TelemetryController')) const TelemetryController = lazy(() => import( './TelemetryController'))
const VisitLog = lazy(() => import('./VisitLog')) const VisitLog = lazy(() => import( './VisitLog'))
const rootPath = '/admin'
export const AdminPanel = () => { export const AdminPanel = () => {
const { tab } = useParams() const { tab } = useParams()
const rootPath = '/admin'
return ( return (
<Layout> <Layout>
<Menu mode={'horizontal'} selectable={true} selectedKeys={[tab]}> <Menu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
<PrivateMenuItem key={'deposit'} permission={'deposit_editor'}> <PrivateMenuItem.Link root={rootPath} key={'deposit' } path={'deposit' } title={'Месторождения' } />
<Link to={`${rootPath}/deposit`}>Месторождения</Link> <PrivateMenuItem.Link root={rootPath} key={'cluster' } path={'cluster' } title={'Кусты' } />
</PrivateMenuItem> <PrivateMenuItem.Link root={rootPath} key={'well' } path={'well' } title={'Скважины' } />
<PrivateMenuItem key={'cluster'} permission={'cluster_editor'}> <PrivateMenuItem.Link root={rootPath} key={'user' } path={'user' } title={'Пользователи' } />
<Link to={`${rootPath}/cluster`}>Кусты</Link> <PrivateMenuItem.Link root={rootPath} key={'company' } path={'company' } title={'Компании' } />
</PrivateMenuItem> <PrivateMenuItem.Link root={rootPath} key={'company_type'} path={'company_type'} title={'Типы компаний' } />
<PrivateMenuItem key={'well'} permission={'well_editor'}> <PrivateMenuItem.Link root={rootPath} key={'role' } path={'role' } title={'Роли' } />
<Link to={`${rootPath}/well`}>Скважины</Link> <PrivateMenuItem.Link root={rootPath} key={'permission' } path={'permission' } title={'Разрешения' } />
</PrivateMenuItem> <PrivateMenuItem.Link root={rootPath} key={'telemetry' } path={'telemetry' } title={'Телеметрии' } />
<PrivateMenuItem key={'user'} permission={'user_editor'}> <PrivateMenuItem.Link root={rootPath} key={'visit_log' } path={'visit_log' } title={'Журнал посещений'} />
<Link to={`${rootPath}/user`}>Пользователи</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'company'} permission={'company_editor'}>
<Link to={`${rootPath}/company`}>Компании</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'company_type'}>
<Link to={`${rootPath}/company_type`}>Типы компаний</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'role'} permission={'role_editor'}>
<Link to={`${rootPath}/role`}>Роли</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'permission'} permission={'permission_editor'}>
<Link to={`${rootPath}/permission`}>Разрешения</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'telemetry'}>
<Link to={`${rootPath}/telemetry`}>Телеметрия</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'visit_log'}>
<Link to={`${rootPath}/visit_log`}>Журнал посещений</Link>
</PrivateMenuItem>
</Menu> </Menu>
<Layout> <Layout>
<Layout.Content className={'site-layout-background'}> <Layout.Content className={'site-layout-background'}>
<Suspense fallback={<SuspenseFallback />}> <Suspense fallback={<SuspenseFallback />}>
<Switch> <Switch>
<PrivateRoute permission={ 'deposit_editor'} path={`${rootPath}/deposit` } component={ DepositController} /> <PrivateRoute path={`${rootPath}/deposit` } component={ DepositController} />
<PrivateRoute permission={ 'cluster_editor'} path={`${rootPath}/cluster` } component={ ClusterController} /> <PrivateRoute path={`${rootPath}/cluster` } component={ ClusterController} />
<PrivateRoute permission={ 'well_editor'} path={`${rootPath}/well` } component={ WellController} /> <PrivateRoute path={`${rootPath}/well` } component={ WellController} />
<PrivateRoute permission={ 'user_editor'} path={`${rootPath}/user` } component={ UserController} /> <PrivateRoute path={`${rootPath}/user` } component={ UserController} />
<PrivateRoute permission={ 'company_editor'} path={`${rootPath}/company` } component={ CompanyController} /> <PrivateRoute path={`${rootPath}/company` } component={ CompanyController} />
<PrivateRoute path={`${rootPath}/company_type`} component={CompanyTypeController} /> <PrivateRoute path={`${rootPath}/company_type`} component={CompanyTypeController} />
<PrivateRoute permission={ 'role_editor'} path={`${rootPath}/role` } component={ RoleController} /> <PrivateRoute path={`${rootPath}/role` } component={ RoleController} />
<PrivateRoute permission={'permission_editor'} path={`${rootPath}/permission`} component={PermissionController} /> <PrivateRoute path={`${rootPath}/permission` } component={ PermissionController} />
<PrivateRoute path={`${rootPath}/telemetry`} component={TelemetryController} /> <PrivateRoute path={`${rootPath}/telemetry` } component={ TelemetryController} />
<PrivateRoute path={`${rootPath}/visit_log`} component={VisitLog} /> <PrivateRoute path={`${rootPath}/visit_log` } component={VisitLog} />
<Route path={'/'}> <PrivateDefaultRoute urls={[
<Redirect to={`${rootPath}/visit_log`}/> `${rootPath}/deposit`,
</Route> `${rootPath}/cluster`,
`${rootPath}/well`,
`${rootPath}/user`,
`${rootPath}/company`,
`${rootPath}/company_type`,
`${rootPath}/role`,
`${rootPath}/permission`,
`${rootPath}/telemetry`,
`${rootPath}/visit_log`,
]}/>
</Switch> </Switch>
</Suspense> </Suspense>
</Layout.Content> </Layout.Content>

View File

@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'
import { DatePicker, Button, Input } from 'antd' import { DatePicker, Button, Input } from 'antd'
import { FileService } from '@api' import { FileService } from '@api'
import { hasPermission } from '@utils/permissions'
import LoaderPortal from '@components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import { UploadForm } from '@components/UploadForm' import { UploadForm } from '@components/UploadForm'
import { CompanyView, UserView } from '@components/views' import { CompanyView, UserView } from '@components/views'
@ -145,6 +146,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
</datalist> </datalist>
</div> </div>
&nbsp;&nbsp; &nbsp;&nbsp;
{hasPermission(`File.edit${idCategory}`) && (
<div> <div>
<span>Загрузка</span> <span>Загрузка</span>
<UploadForm <UploadForm
@ -154,6 +156,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
onUploadComplete={handleUploadComplete} onUploadComplete={handleUploadComplete}
/> />
</div> </div>
)}
&nbsp;&nbsp; &nbsp;&nbsp;
{headerChild} {headerChild}
</div> </div>
@ -167,7 +170,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
showSizeChanger: false, showSizeChanger: false,
onChange: (page) => setPage(page), onChange: (page) => setPage(page),
}} }}
onRowDelete={handleFileDelete} onRowDelete={hasPermission(`File.edit${idCategory}`) && handleFileDelete}
rowKey={(record) => record.id} rowKey={(record) => record.id}
/> />
</LoaderPortal> </LoaderPortal>

View File

@ -1,10 +1,10 @@
import path from 'path'
import { memo } from 'react' import { memo } from 'react'
import { Layout, Menu } from 'antd' import { Layout, Menu } from 'antd'
import { FolderOutlined } from '@ant-design/icons' import { FolderOutlined } from '@ant-design/icons'
import { Switch, useParams, Link, Route } from 'react-router-dom' import { Switch, useParams } from 'react-router-dom'
import { PrivateMenuItem } from '../../components/Private'
import { isInRole } from '../../utils/permissions' import { PrivateDefaultRoute, PrivateMenuItem, PrivateRoute } from '@components/Private'
import DocumentsTemplate from './DocumentsTemplate' import DocumentsTemplate from './DocumentsTemplate'
const { Content } = Layout const { Content } = Layout
@ -22,45 +22,33 @@ export const documentCategories = [
{ id: 9, key: 'closingService', title: 'Сервис по заканчиванию скважины' }, { id: 9, key: 'closingService', title: 'Сервис по заканчиванию скважины' },
] ]
const getUserCategories = () => documentCategories.filter(cat => isInRole(cat.roles))
export const makeMenuItems = (root, cats) => (cats ?? getUserCategories()).map(category => (
<PrivateMenuItem
key={`${category.key}`}
className={'ant-menu-item'}
icon={<FolderOutlined/>}
permission={category.permission}
roles={category.roles}
>
<Link to={{ pathname: `${root}/${category.key}` }}>{category.title}</Link>
</PrivateMenuItem>
))
export const MenuDocuments = memo(({ idWell }) => { export const MenuDocuments = memo(({ idWell }) => {
const { category } = useParams() const { category } = useParams()
const rootPath = `/well/${idWell}` const root = `/well/${idWell}/document`
const root = path.join(rootPath, '/document')
const categories = getUserCategories()
return ( return (
<> <>
<Menu <Menu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[category]}>
mode={'horizontal'} {documentCategories.map(category => (
selectable={true} <PrivateMenuItem.Link
className={'well_menu'} root={root}
selectedKeys={[category]} key={`${category.key}`}
> path={`${category.key}`}
{makeMenuItems(root, categories)} className={'ant-menu-item'}
icon={<FolderOutlined/>}
title={category.title}
/>
))}
</Menu> </Menu>
<Layout> <Layout>
<Content className={'site-layout-background'}> <Content className={'site-layout-background'}>
<Switch> <Switch>
{categories.map(category => ( {documentCategories.map(category => (
<Route path={`${root}/${category.key}`} key={`${category.key}`}> <PrivateRoute path={`${root}/${category.key}`} key={`${category.key}`}>
<DocumentsTemplate idCategory={category.id} idWell={idWell}/> <DocumentsTemplate idCategory={category.id} idWell={idWell}/>
</Route> </PrivateRoute>
))} ))}
<PrivateDefaultRoute urls={documentCategories.map((cat) => `${root}/${cat.key}`)}/>
</Switch> </Switch>
</Content> </Content>
</Layout> </Layout>

View File

@ -1,37 +1,42 @@
import { Redirect, Route, Switch } from 'react-router-dom' import { memo } from 'react'
import { PrivateRoute } from '../components/Private' import { Route, Switch } from 'react-router-dom'
import { AdminLayoutPortal, LayoutPortal } from '../components/Layout'
import Deposit from './Deposit'
import Cluster from './Cluster'
import Well from './Well'
import AdminPanel from './AdminPanel'
export const Main = () => ( import { AdminLayoutPortal, LayoutPortal } from '@components/Layout'
import { PrivateDefaultRoute, PrivateRoute } from '@components/Private'
import Well from './Well'
import Cluster from './Cluster'
import Deposit from './Deposit'
import AdminPanel from './AdminPanel'
import AccessDenied from './AccessDenied'
export const Main = memo(() => (
<Switch> <Switch>
<PrivateRoute path={'/admin/:tab?'} permission={'admin_panel'}> <PrivateRoute path={'/admin/:tab?'}>
<AdminLayoutPortal title={'Администраторская панель'}> <AdminLayoutPortal title={'Администраторская панель'}>
<AdminPanel /> <AdminPanel />
</AdminLayoutPortal> </AdminLayoutPortal>
</PrivateRoute> </PrivateRoute>
<Route path={'/deposit'}> <PrivateRoute path={'/deposit'}>
<LayoutPortal noSheet title='Месторождение'> <LayoutPortal noSheet title='Месторождение'>
<Deposit /> <Deposit />
</LayoutPortal> </LayoutPortal>
</Route> </PrivateRoute>
<Route path={'/cluster/:idClaster/:tab?'}> <PrivateRoute path={'/cluster/:idCluster'}>
<LayoutPortal title={'Анализ скважин куста'}> <LayoutPortal title={'Анализ скважин куста'}>
<Cluster /> <Cluster />
</LayoutPortal> </LayoutPortal>
</Route> </PrivateRoute>
<Route path={'/well/:idWell/:tab?'}> <PrivateRoute path={'/well/:idWell/:tab?'}>
<LayoutPortal> <LayoutPortal>
<Well /> <Well />
</LayoutPortal> </LayoutPortal>
</PrivateRoute>
<Route path={'/access_denied'}>
<AccessDenied />
</Route> </Route>
<Route path={'/'}> <PrivateDefaultRoute urls={['/deposit']} />
<Redirect to={{ pathname: `/deposit` }} />
</Route>
</Switch> </Switch>
) ))
export default Main export default Main

View File

@ -1,56 +1,57 @@
import {Layout, Menu} from "antd"; import { memo } from 'react'
import {Switch, Link, Route, useParams, Redirect} from "react-router-dom"; import { Layout, Menu } from 'antd'
import { FolderOutlined } from "@ant-design/icons"; import { FolderOutlined } from '@ant-design/icons'
import TelemetryAnalysisOperationsSummary from "./TelemetryAnalysisOperationsSummary"; import { Switch, useParams } from 'react-router-dom'
import TelemetryAnalysisOperationsToInterval from "./TelemetryAnalysisOperationsToInterval";
import { PrivateDefaultRoute, PrivateRoute } from '@components/Private'
import { PrivateMenuItemLink } from '@components/Private/PrivateMenuItem'
import TelemetryAnalysisDepthToDay from './TelemetryAnalysisDepthToDay' import TelemetryAnalysisDepthToDay from './TelemetryAnalysisDepthToDay'
import TelemetryAnalysisDepthToInterval from './TelemetryAnalysisDepthToInterval' import TelemetryAnalysisDepthToInterval from './TelemetryAnalysisDepthToInterval'
import TelemetryAnalysisOperationsSummary from './TelemetryAnalysisOperationsSummary'
import TelemetryAnalysisOperationsToInterval from './TelemetryAnalysisOperationsToInterval'
const { Content } = Layout const { Content } = Layout
export default function TelemetryAnalysis({idWell}) { export const TelemetryAnalysis = memo(({ idWell }) => {
let {tab} = useParams() const { tab } = useParams()
const rootPath = `/well/${idWell}/telemetryAnalysis`; const rootPath = `/well/${idWell}/telemetryAnalysis`
return (<> return (
<Menu <>
mode={'horizontal'} <Menu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
selectable={true} <PrivateMenuItemLink root={rootPath} icon={<FolderOutlined />} key={'depthToDay'} path={'depthToDay'} title={'Глубина-день'} />
className={'well_menu'} <PrivateMenuItemLink root={rootPath} icon={<FolderOutlined />} key={'depthToInterval'} path={'depthToInterval'} title={'Глубина-интервал'} />
selectedKeys={[tab]}> <PrivateMenuItemLink root={rootPath} icon={<FolderOutlined />} key={'operationsSummary'} path={'operationsSummary'} title={'Все операции'} />
<Menu.Item key={'depthToDay'} icon={<FolderOutlined />}> <PrivateMenuItemLink root={rootPath} icon={<FolderOutlined />} key={'operationsToInterval'} path={'operationsToInterval'} title={'Операции-интервал'} />
<Link to={`${rootPath}/depthToDay`}>Глубина-день</Link>
</Menu.Item>
<Menu.Item key={'depthToInterval'} icon={<FolderOutlined />}>
<Link to={`${rootPath}/depthToInterval`}>Глубина-интервал</Link>
</Menu.Item>
<Menu.Item key={'operationsSummary'} icon={<FolderOutlined />}>
<Link to={`${rootPath}/operationsSummary`}>Все операции</Link>
</Menu.Item>
<Menu.Item key={'operationsToInterval'} icon={<FolderOutlined />}>
<Link to={`${rootPath}/operationsToInterval`}>Операции-интервал</Link>
</Menu.Item>
</Menu> </Menu>
<Layout> <Layout>
<Content className={'site-layout-background'}> <Content className={'site-layout-background'}>
<Switch> <Switch>
<Route path={`${rootPath}/depthToDay`}> <PrivateRoute root={rootPath} path={'depthToDay'}>
<TelemetryAnalysisDepthToDay idWell={idWell}/> <TelemetryAnalysisDepthToDay idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/depthToInterval`}> <PrivateRoute root={rootPath} path={'depthToInterval'}>
<TelemetryAnalysisDepthToInterval idWell={idWell}/> <TelemetryAnalysisDepthToInterval idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/operationsSummary`}> <PrivateRoute root={rootPath} path={'operationsSummary'}>
<TelemetryAnalysisOperationsSummary idWell={idWell}/> <TelemetryAnalysisOperationsSummary idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/operationsToInterval`}> <PrivateRoute root={rootPath} path={'operationsToInterval'}>
<TelemetryAnalysisOperationsToInterval idWell={idWell}/> <TelemetryAnalysisOperationsToInterval idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={`/`}> <PrivateDefaultRoute urls={[
<Redirect to={`${rootPath}/depthToDay`} /> `${rootPath}/depthToDay`,
</Route> `${rootPath}/depthToInterval`,
`${rootPath}/operationsSummary`,
`${rootPath}/operationsToInterval`,
]}/>
</Switch> </Switch>
</Content> </Content>
</Layout> </Layout>
</>) </>
} )
})
export default TelemetryAnalysis

View File

@ -1,4 +1,4 @@
import { Layout, Menu } from 'antd' import { memo } from 'react'
import { import {
FolderOutlined, FolderOutlined,
FundViewOutlined, FundViewOutlined,
@ -8,9 +8,10 @@ import {
ExperimentOutlined, ExperimentOutlined,
FundProjectionScreenOutlined, FundProjectionScreenOutlined,
} from '@ant-design/icons' } from '@ant-design/icons'
import { Link, Redirect, Route, Switch, useParams } from 'react-router-dom' import { Layout, Menu } from 'antd'
import { Switch, useParams } from 'react-router-dom'
import { PrivateMenuItem } from '@components/Private' import { PrivateRoute, PrivateDefaultRoute, PrivateMenuItem } from '@components/Private'
import Report from './Report' import Report from './Report'
import Archive from './Archive' import Archive from './Archive'
@ -18,102 +19,76 @@ import Measure from './Measure'
import Messages from './Messages' import Messages from './Messages'
import Documents from './Documents' import Documents from './Documents'
import TelemetryView from './TelemetryView' import TelemetryView from './TelemetryView'
import { makeMenuItems } from './Documents'
import WellOperations from './WellOperations' import WellOperations from './WellOperations'
import DrillingProgram from './DrillingProgram' import DrillingProgram from './DrillingProgram'
import TelemetryAnalysis from './TelemetryAnalysis' import TelemetryAnalysis from './TelemetryAnalysis'
const { Content } = Layout const { Content } = Layout
const { SubMenu } = Menu
export const Well = () => { export const Well = memo(() => {
const { idWell, tab } = useParams() const { idWell, tab } = useParams()
const rootPath = `/well/${idWell}` const rootPath = `/well/${idWell}`
return ( return (
<Layout> <Layout>
<Menu <Menu mode={'horizontal'} selectable={true} selectedKeys={[tab]} className={'well_menu'}>
mode={'horizontal'} <PrivateMenuItem.Link root={rootPath} key={'telemetry'} path={'telemetry'} icon={<FundViewOutlined />} title={'Мониторинг'}/>
selectable={true} <PrivateMenuItem.Link root={rootPath} key={'message'} path={'message'} icon={<AlertOutlined/>} title={'Сообщения'} />
selectedKeys={[tab]} <PrivateMenuItem.Link root={rootPath} key={'report'} path={'report'} icon={<FilePdfOutlined />} title={'Рапорт'} />
className={'well_menu'} <PrivateMenuItem.Link root={rootPath} key={'operations'} path={'operations'} icon={<FolderOutlined />} title={'Операции по скважине'} />
> <PrivateMenuItem.Link root={rootPath} key={'archive'} path={'archive'} icon={<DatabaseOutlined />} title={'Архив'} />
<PrivateMenuItem key={'telemetry'} icon={<FundViewOutlined />}> <PrivateMenuItem.Link root={rootPath} key={'telemetryAnalysis'} path={'telemetryAnalysis'} icon={<FundProjectionScreenOutlined />} title={'Операции по телеметрии'} />
<Link to={`${rootPath}/telemetry`}>Мониторинг</Link> <PrivateMenuItem.Link root={rootPath} key={'document'} path={'document'} icon={<FolderOutlined />} title={'Документы'} />
</PrivateMenuItem> <PrivateMenuItem.Link root={rootPath} key={'measure'} path={'measure'} icon={<ExperimentOutlined />} title={'Измерения'} />
<PrivateMenuItem key={'message'} icon={<AlertOutlined/>}> <PrivateMenuItem.Link root={rootPath} key={'drillingProgram'} path={'drillingProgram'} icon={<FolderOutlined />} title={'Программа бурения'} />
<Link to={`${rootPath}/message`}>Сообщения</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'report'} icon={<FilePdfOutlined />}>
<Link to={`${rootPath}/report`}>Рапорт</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'operations'} icon={<FolderOutlined />}>
<Link to={`${rootPath}/operations`}>Операции по скважине</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'archive'} icon={<DatabaseOutlined />}>
<Link to={`${rootPath}/archive`}>Архив</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'telemetryAnalysis'} icon={<FundProjectionScreenOutlined />} roles={'admin'}>
<Link to={`${rootPath}/telemetryAnalysis/depthToDay`}>Операции по телеметрии</Link>
</PrivateMenuItem>
<SubMenu
key={'document'}
title={
<Link to={`${rootPath}/document/fluidService`} className={'linkDocuments'}>
Документы
</Link>
}
icon={<FolderOutlined />}
selectable={true}
>
{makeMenuItems(`${rootPath}/document`)}
</SubMenu>
<PrivateMenuItem key={'measure'} icon={<ExperimentOutlined />}>
<Link to={`${rootPath}/measure`}>Измерения</Link>
</PrivateMenuItem>
<PrivateMenuItem key={'drillingProgram'} icon={<FolderOutlined />}>
<Link to={`${rootPath}/drillingProgram`}>Программа бурения</Link>
</PrivateMenuItem>
</Menu> </Menu>
<Layout> <Layout>
<Content className={'site-layout-background'}> <Content className={'site-layout-background'}>
<Switch> <Switch>
<Route path={'/well/:idWell/telemetry'}> <PrivateRoute path={`${rootPath}/telemetry`}>
<TelemetryView idWell={idWell} /> <TelemetryView idWell={idWell} />
</Route> </PrivateRoute>
<Route path={'/well/:idWell/message'}> <PrivateRoute path={`${rootPath}/message`}>
<Messages idWell={idWell} /> <Messages idWell={idWell} />
</Route> </PrivateRoute>
<Route path={'/well/:idWell/report'}> <PrivateRoute path={`${rootPath}/report`}>
<Report idWell={idWell} /> <Report idWell={idWell} />
</Route> </PrivateRoute>
<Route path={'/well/:idWell/operations/:tab?'}> <PrivateRoute path={`${rootPath}/operations/:tab?`}>
<WellOperations idWell={idWell} /> <WellOperations idWell={idWell} />
</Route> </PrivateRoute>
<Route path={'/well/:idWell/archive'}> <PrivateRoute path={`${rootPath}/archive`}>
<Archive idWell={idWell} /> <Archive idWell={idWell} />
</Route> </PrivateRoute>
<Route path={'/well/:idWell/telemetryAnalysis/:tab'}> <PrivateRoute path={`${rootPath}/telemetryAnalysis/:tab`}>
<TelemetryAnalysis idWell={idWell} /> <TelemetryAnalysis idWell={idWell} />
</Route> </PrivateRoute>
<Route path={'/well/:idWell/document/:category'}> <PrivateRoute path={`${rootPath}/document/:category`}>
<Documents idWell={idWell} /> <Documents idWell={idWell} />
</Route> </PrivateRoute>
<Route path={'/well/:id/measure'}> <PrivateRoute path={`${rootPath}/measure`}>
<Measure idWell={idWell}/> <Measure idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={'/well/:id/drillingProgram'}> <PrivateRoute path={`${rootPath}/drillingProgram`}>
<DrillingProgram idWell={idWell}/> <DrillingProgram idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={'/'}> <PrivateDefaultRoute urls={[
<Redirect to={`${rootPath}/telemetry`} /> `${rootPath}/telemetry`,
</Route> `${rootPath}/message`,
`${rootPath}/report`,
`${rootPath}/operations`,
`${rootPath}/archive`,
`${rootPath}/telemetryAnalysis`,
`${rootPath}/document`,
`${rootPath}/measure`,
`${rootPath}/drillingProgram`,
]}/>
</Switch> </Switch>
</Content> </Content>
</Layout> </Layout>
</Layout> </Layout>
) )
} })
export default Well export default Well

View File

@ -1,13 +1,15 @@
import { useState, useEffect } from 'react' import { useState, useEffect, memo } from 'react'
import { import {
EditableTable, EditableTable,
makeNumericMinMax, makeNumericMinMax,
makeNumericStartEnd, makeNumericStartEnd,
} from '../../components/Table' } from '@components/Table'
import LoaderPortal from '../../components/LoaderPortal' import { DrillFlowChartService } from '@api'
import { invokeWebApiWrapperAsync } from '../../components/factory' import LoaderPortal from '@components/LoaderPortal'
import { DrillFlowChartService } from '../../services/api' import { invokeWebApiWrapperAsync } from '@components/factory'
import { arrayOrDefault } from '../../utils' import { hasPermission } from '@utils/permissions'
import { arrayOrDefault } from '@utils'
const columns = [ const columns = [
makeNumericStartEnd('Глубина, м', 'depth'), makeNumericStartEnd('Глубина, м', 'depth'),
@ -18,7 +20,7 @@ const columns = [
makeNumericMinMax('Расход, л/с', 'flow') makeNumericMinMax('Расход, л/с', 'flow')
] ]
export const DrillProcessFlow = ({ idWell }) => { export const DrillProcessFlow = memo(({ idWell }) => {
const [flows, setFlows] = useState([]) const [flows, setFlows] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
@ -59,11 +61,13 @@ export const DrillProcessFlow = ({ idWell }) => {
bordered bordered
columns={columns} columns={columns}
dataSource={flows} dataSource={flows}
onRowAdd={onAdd} onRowAdd={hasPermission('DrillFlowChart.edit') && onAdd}
onRowEdit={onEdit} onRowEdit={hasPermission('DrillFlowChart.edit') && onEdit}
onRowDelete={onDelete} onRowDelete={hasPermission('DrillFlowChart.delete') && onDelete}
pagination={false} pagination={false}
/> />
</LoaderPortal> </LoaderPortal>
) )
} })
export default DrillProcessFlow

View File

@ -1,12 +1,14 @@
import { memo, useState } from 'react'
import { Button, Tooltip, Modal } from 'antd' import { Button, Tooltip, Modal } from 'antd'
import {useState} from 'react'
import { FileOutlined, ImportOutlined, ExportOutlined } from '@ant-design/icons' import { FileOutlined, ImportOutlined, ExportOutlined } from '@ant-design/icons'
import { download } from '../../components/factory'
import { download } from '@components/factory'
import { ImportOperations } from './ImportOperations' import { ImportOperations } from './ImportOperations'
const style = {margin:4} const style = { margin: 4 }
export const ImportExportBar = ({idWell, onImported, disabled}) =>{ export const ImportExportBar = memo(({ idWell, onImported, disabled }) => {
const [isImportModalVisible, setIsImportModalVisible] = useState(false) const [isImportModalVisible, setIsImportModalVisible] = useState(false)
const downloadTemplate = async () => download(`/api/well/${idWell}/wellOperations/template`) const downloadTemplate = async () => download(`/api/well/${idWell}/wellOperations/template`)
@ -14,20 +16,20 @@ export const ImportExportBar = ({idWell, onImported, disabled}) =>{
const onDone = () => { const onDone = () => {
setIsImportModalVisible(false) setIsImportModalVisible(false)
if(onImported) onImported?.()
onImported()
} }
return <> return (
<Tooltip title = 'Импорт - загрузить файл с операциями на сервер'> <>
<Tooltip title={'Импорт - загрузить файл с операциями на сервер'}>
<Button <Button
disabled={disabled} disabled={disabled}
icon={<ImportOutlined/>} icon={<ImportOutlined/>}
style={style} style={style}
onClick={_=>setIsImportModalVisible(true)}/> onClick={() => setIsImportModalVisible(true)}/>
</Tooltip> </Tooltip>
<Tooltip title = 'Экспорт - скачать файл с операциями по скважине'> <Tooltip title={'Экспорт - скачать файл с операциями по скважине'}>
<Button <Button
disabled={disabled} disabled={disabled}
icon={<ExportOutlined/>} icon={<ExportOutlined/>}
@ -35,23 +37,20 @@ export const ImportExportBar = ({idWell, onImported, disabled}) =>{
onClick={downloadExport}/> onClick={downloadExport}/>
</Tooltip> </Tooltip>
<Tooltip title = 'Скачать шаблон для импорта операций'> <Tooltip title={'Скачать шаблон для импорта операций'}>
<Button <Button disabled={disabled} icon={<FileOutlined />} style={style} onClick={downloadTemplate} />
disabled={disabled}
icon={<FileOutlined/>}
style={style}
onClick={downloadTemplate}/>
</Tooltip> </Tooltip>
<Modal <Modal
title='Импорт операций' title={'Импорт операций'}
visible={isImportModalVisible} visible={isImportModalVisible}
onCancel={_=>setIsImportModalVisible(false)} onCancel={() => setIsImportModalVisible(false)}
footer={null}
footer={null}> >
<ImportOperations <ImportOperations idWell={idWell} onDone={onDone} />
idWell={idWell} </Modal>
onDone={onDone}/>
</Modal >
</> </>
} )
})
export default ImportExportBar

View File

@ -1,3 +1,4 @@
import { Switch } from 'antd'
import { memo, useState, useRef, useEffect } from 'react' import { memo, useState, useRef, useEffect } from 'react'
import { import {
@ -13,12 +14,42 @@ import 'chartjs-adapter-moment'
import zoomPlugin from 'chartjs-plugin-zoom' import zoomPlugin from 'chartjs-plugin-zoom'
import ChartDataLabels from 'chartjs-plugin-datalabels' import ChartDataLabels from 'chartjs-plugin-datalabels'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { getOperations } from '@pages/Cluster/functions'
import LoaderPortal from '@components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { getOperations } from '@pages/Cluster/functions'
Chart.register(TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin) Chart.register(TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin)
const scaleTypes = {
day: {
min: 0,
type: 'linear',
display: true,
title: { display: false, text: '' },
ticks: { stepSize: 1 }
},
date: {
display: true,
title: { display: true },
type: 'time',
time: {
unit: 'hour',
displayFormats: { 'hour': 'MM.DD' }
},
grid: { drawTicks: true },
ticks: {
stepSize: 3,
major: { enabled: true },
z: 1,
display: true,
textStrokeColor: '#fff',
textStrokeWidth: 2,
color: '#000',
}
}
}
const defaultOptions = { const defaultOptions = {
responsive: true, responsive: true,
aspectRatio: 2.6, aspectRatio: 2.6,
@ -27,20 +58,8 @@ const defaultOptions = {
mode: 'index', mode: 'index',
}, },
scales: { scales: {
x: { x: scaleTypes.day,
min: 0, y: {
type: 'linear',
display: true,
title: {
display: false,
text: '',
},
ticks: {
stepSize: 1,
}
},
y:{
type: 'linear', type: 'linear',
position: 'top', position: 'top',
reverse: true, reverse: true,
@ -90,7 +109,8 @@ const makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({
export const Tvd = memo(({ idWell, title }) => { export const Tvd = memo(({ idWell, title }) => {
const [operations, setOperations] = useState([]) const [operations, setOperations] = useState([])
const [showLoader, setShowLoader] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [xLabel, setXLabel] = useState('day')
const [chart, setChart] = useState() const [chart, setChart] = useState()
const chartRef = useRef(null) const chartRef = useRef(null)
@ -101,7 +121,7 @@ export const Tvd = memo(({ idWell, title }) => {
const operations = await getOperations(idWell) const operations = await getOperations(idWell)
setOperations(operations) setOperations(operations)
}, },
setShowLoader, setIsLoading,
`Не удалось загрузить операции по скважине "${idWell}"`, `Не удалось загрузить операции по скважине "${idWell}"`,
) )
}, [idWell]) }, [idWell])
@ -118,6 +138,8 @@ export const Tvd = memo(({ idWell, title }) => {
if (chartRef.current && !chart) { if (chartRef.current && !chart) {
const thisOptions = {} const thisOptions = {}
Object.assign(thisOptions, defaultOptions) Object.assign(thisOptions, defaultOptions)
thisOptions.scales.x = scaleTypes[xLabel]
thisOptions.parsing.xAxisKey = xLabel
const newChart = new Chart(chartRef.current, { const newChart = new Chart(chartRef.current, {
type: 'line', type: 'line',
@ -130,9 +152,11 @@ export const Tvd = memo(({ idWell, title }) => {
return () => chart?.destroy() return () => chart?.destroy()
} else { } else {
chart.data = data chart.data = data
chart.options.scales.x = scaleTypes[xLabel]
chart.options.parsing.xAxisKey = xLabel
chart.update() chart.update()
} }
}, [chart, operations]) }, [chart, operations, xLabel])
return ( return (
<div className={'container'}> <div className={'container'}>
@ -140,10 +164,20 @@ export const Tvd = memo(({ idWell, title }) => {
{title || ( {title || (
<h2 className={'mt-20px'}>График Глубина-день</h2> <h2 className={'mt-20px'}>График Глубина-день</h2>
)} )}
<LoaderPortal show={showLoader}> <LoaderPortal show={isLoading}>
<canvas ref={chartRef} /> <canvas ref={chartRef} />
<div style={{ display: 'flex', justifyContent: 'center', width: '100%' }}>
<Switch
checkedChildren={'Дата'}
unCheckedChildren={'Дни со старта'}
loading={isLoading}
onChange={(checked) => setXLabel(checked ? 'date' : 'day')}
/>
</div>
</LoaderPortal> </LoaderPortal>
</div> </div>
</div> </div>
) )
}) })
export default Tvd

View File

@ -1,20 +1,20 @@
import { Link } from 'react-router-dom'
import { useState, useEffect, memo } from 'react'
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons' import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
import { Table, Tag, Button, Badge, Divider, Modal, Row, Col, Popconfirm } from 'antd' import { Table, Tag, Button, Badge, Divider, Modal, Row, Col, Popconfirm } from 'antd'
import { Link } from 'react-router-dom'
import { useState, useEffect } from 'react'
import { makeTextColumn, makeNumericColumnPlanFact } from '../../../components/Table' import { CompanyView } from '@components/views'
import { DrillParamsService, WellCompositeService } from '../../../services/api' import LoaderPortal from '@components/LoaderPortal'
import LoaderPortal from '../../../components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory'
import { invokeWebApiWrapperAsync } from '../../../components/factory' import { makeTextColumn, makeNumericColumnPlanFact } from '@components/Table'
import { CompanyView } from '../../../components/views' import { DrillParamsService, WellCompositeService } from '@api'
import { import {
calcAndUpdateStatsBySections, calcAndUpdateStatsBySections,
makeFilterMinMaxFunction, makeFilterMinMaxFunction,
getOperations getOperations
} from '../../Cluster/functions' } from '@pages/Cluster/functions'
import WellOperationsTable from '../../Cluster/WellOperationsTable' import WellOperationsTable from '@pages/Cluster/WellOperationsTable'
import { Tvd } from '../Tvd' import { Tvd } from '../Tvd'
import { getColumns } from '../WellDrillParams' import { getColumns } from '../WellDrillParams'
@ -27,7 +27,7 @@ const filtersMinMax = [
const filtersSectionsType = [] const filtersSectionsType = []
const DAY_IN_MS = 1000 * 60 * 60 * 24 const DAY_IN_MS = 1000 * 60 * 60 * 24
export const WellCompositeSections = ({ idWell, statsWells, selectedSections }) => { export const WellCompositeSections = memo(({ idWell, statsWells, selectedSections }) => {
const [rows, setRows] = useState([]) const [rows, setRows] = useState([])
const [params, setParams] = useState([]) const [params, setParams] = useState([])
const [paramsColumns, setParamsColumns] = useState([]) const [paramsColumns, setParamsColumns] = useState([])
@ -288,4 +288,6 @@ export const WellCompositeSections = ({ idWell, statsWells, selectedSections })
</Modal> </Modal>
</> </>
) )
} })
export default WellCompositeSections

View File

@ -1,22 +1,23 @@
import { useState, useEffect } from 'react' import { useState, useEffect, memo } from 'react'
import { Redirect, Route, Switch, Link, useParams } from 'react-router-dom' import { Switch, useParams } from 'react-router-dom'
import { Col, Layout, Menu, Row, Tag, TreeSelect } from 'antd' import { Col, Layout, Menu, Row, Tag, TreeSelect } from 'antd'
import LoaderPortal from '../../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../../components/factory'
import { import {
DepositService, DepositService,
OperationStatService, OperationStatService,
WellCompositeService, WellCompositeService,
} from '../../../services/api' } from '@api'
import { arrayOrDefault } from '../../../utils' import { arrayOrDefault } from '@utils'
import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { PrivateDefaultRoute, PrivateMenuItemLink, PrivateRoute } from '@components/Private'
import ClusterWells from '../../Cluster/ClusterWells' import ClusterWells from '@pages/Cluster/ClusterWells'
import { WellCompositeSections } from './WellCompositeSections' import { WellCompositeSections } from './WellCompositeSections'
const { Content } = Layout const { Content } = Layout
export const WellCompositeEditor = ({ idWell }) => { export const WellCompositeEditor = memo(({ idWell }) => {
const rootPath = `/well/${idWell}/operations/composite` const rootPath = `/well/${idWell}/operations/composite`
const { tab } = useParams() const { tab } = useParams()
@ -94,7 +95,7 @@ export const WellCompositeEditor = ({ idWell }) => {
treeLine={{ showLeafIcon: false }} treeLine={{ showLeafIcon: false }}
onChange={(value) => setSelectedIdWells(value)} onChange={(value) => setSelectedIdWells(value)}
size={'middle'} size={'middle'}
style={{width: '100%'}} style={{ width: '100%' }}
value={selectedIdWells} value={selectedIdWells}
placeholder={'Выберите скважины'} placeholder={'Выберите скважины'}
tagRender={(props) => ( tagRender={(props) => (
@ -103,18 +104,9 @@ export const WellCompositeEditor = ({ idWell }) => {
/> />
</Col> </Col>
<Col span={6}> <Col span={6}>
<Menu <Menu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
mode={'horizontal'} <PrivateMenuItemLink root={rootPath} key={'wells'} path={'wells'} title={'Статистика по скважинам'} />
selectable={true} <PrivateMenuItemLink root={rootPath} key={'sections'} path={'sections'} title={'Статистика по секциям'} />
className={'well_menu'}
selectedKeys={[tab]}
>
<Menu.Item key={'wells'}>
<Link to={`${rootPath}/wells`}>Статистика по скважинам</Link>
</Menu.Item>
<Menu.Item key={'sections'}>
<Link to={`${rootPath}/sections`}>Статистика по секциям</Link>
</Menu.Item>
</Menu> </Menu>
</Col> </Col>
</Row> </Row>
@ -122,23 +114,23 @@ export const WellCompositeEditor = ({ idWell }) => {
<Content className={'site-layout-background'}> <Content className={'site-layout-background'}>
<LoaderPortal show={showTabLoader}> <LoaderPortal show={showTabLoader}>
<Switch> <Switch>
<Route path={`${rootPath}/wells`}> <PrivateRoute path={`${rootPath}/wells`}>
<ClusterWells statsWells={statsWells}/> <ClusterWells statsWells={statsWells}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/sections`}> <PrivateRoute path={`${rootPath}/sections`}>
<WellCompositeSections <WellCompositeSections
idWell={idWell} idWell={idWell}
statsWells={statsWells} statsWells={statsWells}
selectedSections={selectedSections} selectedSections={selectedSections}
/> />
</Route> </PrivateRoute>
<Route path={rootPath}> <PrivateDefaultRoute urls={[`${rootPath}/wells`, `${rootPath}/sections`]}/>
<Redirect to={`${rootPath}/wells`}/>
</Route>
</Switch> </Switch>
</LoaderPortal> </LoaderPortal>
</Content> </Content>
</Layout> </Layout>
</LoaderPortal> </LoaderPortal>
) )
} })
export default WellCompositeEditor

View File

@ -1,15 +1,16 @@
import { useState, useEffect, useCallback } from 'react' import { useState, useEffect, useCallback, memo } from 'react'
import { DrillParamsService, WellOperationService } from '../../services/api'
import LoaderPortal from '../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { import {
EditableTable, EditableTable,
makeSelectColumn, makeSelectColumn,
makeActionHandler, makeActionHandler,
makeNumericAvgRange, makeNumericAvgRange,
} from '../../components/Table' } from '@components/Table'
import { arrayOrDefault } from '../../utils' import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { DrillParamsService, WellOperationService } from '@api'
import { hasPermission } from '@utils/permissions'
import { arrayOrDefault } from '@utils'
export const getColumns = async (idWell) => { export const getColumns = async (idWell) => {
let sectionTypes = await WellOperationService.getSectionTypes(idWell) let sectionTypes = await WellOperationService.getSectionTypes(idWell)
@ -31,7 +32,7 @@ export const getColumns = async (idWell) => {
] ]
} }
export const WellDrillParams = ({ idWell }) => { export const WellDrillParams = memo(({ idWell }) => {
const [params, setParams] = useState([]) const [params, setParams] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
const [columns, setColumns] = useState([]) const [columns, setColumns] = useState([])
@ -69,11 +70,13 @@ export const WellDrillParams = ({ idWell }) => {
bordered bordered
columns={columns} columns={columns}
dataSource={params} dataSource={params}
onRowAdd={makeActionHandler('insert', handlerProps, recordParser)} onRowAdd={hasPermission('DrillParams.edit') && makeActionHandler('insert', handlerProps, recordParser)}
onRowEdit={makeActionHandler('update', handlerProps, recordParser)} onRowEdit={hasPermission('DrillParams.edit') && makeActionHandler('update', handlerProps, recordParser)}
onRowDelete={makeActionHandler('delete', handlerProps, recordParser)} onRowDelete={hasPermission('DrillParams.delete') && makeActionHandler('delete', handlerProps, recordParser)}
pagination={false} pagination={false}
/> />
</LoaderPortal> </LoaderPortal>
) )
} })
export default WellDrillParams

View File

@ -1,6 +1,6 @@
import moment from 'moment' import moment from 'moment'
import { Input } from 'antd' import { Input } from 'antd'
import { useState, useEffect } from 'react' import { useState, useEffect, memo } from 'react'
import { import {
EditableTable, EditableTable,
@ -10,11 +10,12 @@ import {
makeNumericColumnOptions, makeNumericColumnOptions,
makeSelectColumn, makeSelectColumn,
makeActionHandler, makeActionHandler,
} from '../../components/Table' } from '@components/Table'
import LoaderPortal from '../../components/LoaderPortal' import { WellOperationService} from '@api'
import { invokeWebApiWrapperAsync } from '../../components/factory' import LoaderPortal from '@components/LoaderPortal'
import { WellOperationService} from '../../services/api' import { invokeWebApiWrapperAsync } from '@components/factory'
import { formatDate } from '../../utils' import { hasPermission } from '@utils/permissions'
import { formatDate } from '@utils'
const { TextArea } = Input const { TextArea } = Input
@ -52,7 +53,7 @@ const defaultColumns = [
makeColumn('Комментарий', 'comment', { editable: true, input: <TextArea/> }), makeColumn('Комментарий', 'comment', { editable: true, input: <TextArea/> }),
] ]
export const WellOperationsEditor = ({idWell, idType}) => { export const WellOperationsEditor = memo(({ idWell, idType, ...other }) => {
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({current:1, pageSize:basePageSize}) const [pageNumAndPageSize, setPageNumAndPageSize] = useState({current:1, pageSize:basePageSize})
const [paginationTotal, setPaginationTotal] = useState(0) const [paginationTotal, setPaginationTotal] = useState(0)
const [operations, setOperations] = useState([]) const [operations, setOperations] = useState([])
@ -113,13 +114,14 @@ export const WellOperationsEditor = ({idWell, idType}) => {
return ( return (
<LoaderPortal show={showLoader}> <LoaderPortal show={showLoader}>
<EditableTable <EditableTable
size={'small'} {...other}
bordered bordered
size={'small'}
columns={columns} columns={columns}
dataSource={operations} dataSource={operations}
onRowAdd={makeActionHandler('insertRange', handlerProps, recordParser)} onRowAdd={hasPermission('WellOperation.edit') && makeActionHandler('insertRange', handlerProps, recordParser)}
onRowEdit={makeActionHandler('update', handlerProps, recordParser)} onRowEdit={hasPermission('WellOperation.edit') && makeActionHandler('update', handlerProps, recordParser)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowDelete={hasPermission('WellOperation.delete') && makeActionHandler('delete', handlerProps)}
pagination={{ pagination={{
current: pageNumAndPageSize.current, current: pageNumAndPageSize.current,
pageSize: pageNumAndPageSize.pageSize, pageSize: pageNumAndPageSize.pageSize,
@ -130,4 +132,6 @@ export const WellOperationsEditor = ({idWell, idType}) => {
/> />
</LoaderPortal> </LoaderPortal>
) )
} })
export default WellOperationsEditor

View File

@ -1,5 +1,6 @@
import {Layout, Menu} from "antd"; import { memo } from 'react'
import {Switch, Link, Route, Redirect, useParams, useHistory} from "react-router-dom"; import { Layout, Menu } from 'antd'
import { Switch, useParams, useHistory } from 'react-router-dom'
import { import {
BarChartOutlined, BarChartOutlined,
BuildOutlined, BuildOutlined,
@ -7,84 +8,79 @@ import {
DeploymentUnitOutlined, DeploymentUnitOutlined,
LineChartOutlined, LineChartOutlined,
TableOutlined, TableOutlined,
} from "@ant-design/icons"; } from '@ant-design/icons'
import { PrivateDefaultRoute, PrivateRoute, PrivateMenuItemLink } from '@components/Private'
import { Tvd } from './Tvd'
import { ImportExportBar } from './ImportExportBar'
import { WellDrillParams } from './WellDrillParams' import { WellDrillParams } from './WellDrillParams'
import { DrillProcessFlow } from './DrillProcessFlow'
import { WellSectionsStat } from './WellSectionsStat' import { WellSectionsStat } from './WellSectionsStat'
import { WellCompositeEditor } from './WellCompositeEditor' import { WellCompositeEditor } from './WellCompositeEditor'
import { WellOperationsEditor } from './WellOperationsEditor' import { WellOperationsEditor } from './WellOperationsEditor'
import { Tvd } from './Tvd'
import { ImportExportBar } from "./ImportExportBar";
import { DrillProcessFlow } from "./DrillProcessFlow";
const { Content } = Layout const { Content } = Layout
export default function WellOperations({idWell}) { export const WellOperations = memo(({ idWell }) => {
let {tab} = useParams() const { tab } = useParams()
let history = useHistory() const history = useHistory()
const rootPath = `/well/${idWell}/operations` const rootPath = `/well/${idWell}/operations`
const onImported = () => history.push(`${rootPath}`) const onImported = () => history.push(`${rootPath}`)
const isIEBarDisabled = !["plan", "fact"].includes(tab) const isIEBarDisabled = !['plan', 'fact'].includes(tab)
return(<> return(
<Menu <>
mode="horizontal" <Menu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
selectable={true} <PrivateMenuItemLink root={rootPath} icon={<LineChartOutlined />} key={'tvd'} path={'tvd'} title={'TVD'} />
className="well_menu" <PrivateMenuItemLink root={rootPath} icon={<BuildOutlined />} key={'sections'} path={'sections'} title={'Секции'} />
selectedKeys={[tab]}> <PrivateMenuItemLink root={rootPath} icon={<TableOutlined />} key={'plan'} path={'plan'} title={'План'} />
<Menu.Item key="tvd" icon={<LineChartOutlined />}> <PrivateMenuItemLink root={rootPath} icon={<TableOutlined />} key={'fact'} path={'fact'} title={'Факт'} />
<Link to={`${rootPath}/tvd`}>TVD</Link> <PrivateMenuItemLink root={rootPath} icon={<BarChartOutlined />} key={'drillProcessFlow'} path={'drillProcessFlow'} title={'РТК'} />
</Menu.Item> <PrivateMenuItemLink root={rootPath} icon={<ControlOutlined />} key={'params'} path={'params'} title={'Режимы'} />
<Menu.Item key="sections" icon={<BuildOutlined />}> <PrivateMenuItemLink root={rootPath} icon={<DeploymentUnitOutlined />} key={'composite'} path={'composite'} title={'Аналитика'} />
<Link to={`${rootPath}/sections`}>Секции</Link>
</Menu.Item>
<Menu.Item key="plan" icon={<TableOutlined />}>
<Link to={`${rootPath}/plan`}>План</Link>
</Menu.Item>
<Menu.Item key="fact" icon={<TableOutlined />}>
<Link to={`${rootPath}/fact`}>Факт</Link>
</Menu.Item>
<Menu.Item key="drillProcessFlow" icon={<BarChartOutlined />}>
<Link to={`${rootPath}/drillProcessFlow`}>РТК</Link>
</Menu.Item>
<Menu.Item key="params" icon={<ControlOutlined />}>
<Link to={`${rootPath}/params`}>Режимы</Link>
</Menu.Item>
<Menu.Item key="composite" icon={<DeploymentUnitOutlined />}>
<Link to={`${rootPath}/composite`}>Аналитика</Link>
</Menu.Item>
<ImportExportBar idWell={idWell} disabled={isIEBarDisabled} onImported={onImported}/> <ImportExportBar idWell={idWell} disabled={isIEBarDisabled} onImported={onImported}/>
</Menu> </Menu>
<Layout> <Layout>
<Content className="site-layout-background"> <Content className={'site-layout-background'}>
<Switch> <Switch>
<Route path={`${rootPath}/tvd`}> <PrivateRoute path={`${rootPath}/tvd`}>
<Tvd idWell={idWell}/> <Tvd idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/sections`}> <PrivateRoute path={`${rootPath}/sections`}>
<WellSectionsStat idWell={idWell}/> <WellSectionsStat idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/plan`}> <PrivateRoute path={`${rootPath}/plan`}>
<WellOperationsEditor idWell={idWell} idType={0}/> <WellOperationsEditor idWell={idWell} idType={0} tableName={'well_operations_plan'} showSettingsChanger />
</Route> </PrivateRoute>
<Route path={`${rootPath}/fact`}> <PrivateRoute path={`${rootPath}/fact`}>
<WellOperationsEditor idWell={idWell} idType={1}/> <WellOperationsEditor idWell={idWell} idType={1} tableName={'well_operations_fact'}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/drillProcessFlow`}> <PrivateRoute path={`${rootPath}/drillProcessFlow`}>
<DrillProcessFlow idWell={idWell} /> <DrillProcessFlow idWell={idWell} />
</Route> </PrivateRoute>
<Route path={`${rootPath}/params`}> <PrivateRoute path={`${rootPath}/params`}>
<WellDrillParams idWell={idWell}/> <WellDrillParams idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={`${rootPath}/composite/:tab?`}> <PrivateRoute path={`${rootPath}/composite/:tab?`}>
<WellCompositeEditor idWell={idWell}/> <WellCompositeEditor idWell={idWell}/>
</Route> </PrivateRoute>
<Route path={rootPath}> <PrivateDefaultRoute urls={[
<Redirect to={`${rootPath}/plan`}/> `${rootPath}/plan`,
</Route> `${rootPath}/fact`,
`${rootPath}/tvd`,
`${rootPath}/sections`,
`${rootPath}/drillProcessFlow`,
`${rootPath}/params`,
`${rootPath}/composite`
]}/>
</Switch> </Switch>
</Content> </Content>
</Layout> </Layout>
</>) </>
} )
})
export default WellOperations

View File

@ -3,10 +3,13 @@ import { getUserPermissions, getUserRoles } from './storage'
export type Role = string export type Role = string
export type Permission = string export type Permission = string
export const hasPermission = (permission?: Permission): boolean => { export const hasPermission = (permission?: Permission | Permission[], userPermissions?: Permission[]): boolean => {
if (typeof permission !== 'string') return true if (!Array.isArray(permission) && typeof permission !== 'string')
return true
return getUserPermissions().includes(permission) const userPerms = userPermissions ?? getUserPermissions()
if (typeof permission === 'string')
permission = [permission]
return permission.every((perm) => userPerms.includes(perm))
} }
export const isInRole = (roles?: Role[] | Role): boolean => { export const isInRole = (roles?: Role[] | Role): boolean => {
@ -15,43 +18,104 @@ export const isInRole = (roles?: Role[] | Role): boolean => {
if (!roles?.length) return true if (!roles?.length) return true
const user_roles = getUserRoles() const user_roles = getUserRoles()
return roles.some((role) => role in user_roles) return roles.some((role) => user_roles.includes(role))
} }
/* const sectionAvailable = (section: PermissionRecord, userPermission: Permission[] = getUserPermissions()) => {
deposit for (const child of Object.values(section)) {
cluster if (!child) continue
well (R) if (Array.isArray(child)) {
well/archive (R) if (hasPermission(child, userPermission)) return true
well/message (R) } else {
well/report (R) if (sectionAvailable(child, userPermission)) return true
well/measure (RW) }
well/drillingProgram (RU согласовать) }
well/telemetry (R) return false
well/telemetry:status (RW) }
well/telemetry:rop (RW)
well/operations (R) export const isURLAvailable = (path: string, userPermissions?: Permission[]) => {
well/operations/tvd (R) const pathParts = path.replaceAll('/', ' ').trim().split(' ')
well/operations/sections (R) if (pathParts.length <= 0) return false
well/operations/plan (RW)
well/operations/fact (RW) let perms: PermissionRecord | string[] | null = requirements
well/operations/drillProccesFlow (RW) for (let i = 0; i < pathParts.length; i++) {
well/operations/params (RW) if (!perms) return false
well/operations/composite (R) if (Array.isArray(perms)) break
well/operations/composite/wells (R) if (pathParts[i] in perms) {
well/operations/composite/sections (RW) if (!perms[pathParts[i]]) return false
well/document (R) perms = perms[pathParts[i]]
well/document/fluidService (RU) } else if ('*' in perms) {
well/document/cementing (RU) perms = perms['*']
well/document/nnb (RU) } else {
well/document/gti (RU) if(i + 1 < pathParts.length || pathParts[i][0] !== ':') return false
well/document/documentsForWell (RU) // Если последним аргументом указан параметр, считаем, что началась секция
well/document/supervisor (RU) break
well/document/master (RU) }
admin (R) }
admin/deposit (RAED)
admin/cluster (RAED) if (!perms) return false
admin/well (RAED) const userPerms: string[] = userPermissions ?? getUserPermissions()
admin/user (RAED) if (Array.isArray(perms)) {
admin/company (RAED) return perms.every((perm) => userPerms.includes(perm))
*/ }
return sectionAvailable(perms, userPerms)
}
interface PermissionRecord extends Record<string, string[] | PermissionRecord | null> {}
export const requirements: PermissionRecord = {
'': [],
login: [], // ['Auth.edit']
register: [], // ['Auth.edit']
admin: {
user: ['AdminCompany.get', 'AdminUserRole.get', 'AdminUser.get'],
well: ['AdminCompany.get', 'AdminCluster.get', 'AdminTelemetry.get', 'AdminWell.get'],
cluster: ['AdminCluster.get', 'AdminDeposit.get'],
company: ['AdminCompany.get', 'AdminCompanyType.get'],
company_type: ['AdminCompanyType.get'],
deposit: ['AdminDeposit.get'],
permission: ['AdminPermission.get'],
role: ['AdminUserRole.get', 'AdminPermission.get'],
visit_log: ['RequerstTracker.get'],
telemetry: ['AdminTelemetry.get'],
},
deposit: ['Deposit.get', 'Cluster.get'],
cluster: {
'*': ['Deposit.get', 'Cluster.get', 'OperationStat.get'],
},
well: {
'*': {
document: {
'*': ['Deposit.get', 'File.get'],
},
archive: ['Deposit.get', 'TelemetryDataSaub.get'],
telemetry: ['Deposit.get', 'DrillFlowChart.get', 'TelemetryDataSaub.get', 'TelemetryDataSpin.get'],
message: ['Deposit.get', 'TelemetryDataSaub.get'],
report: ['Deposit.get', 'Report.get'],
operations: {
tvd: ['Deposit.get', 'OperationStat.get'],
sections: ['Deposit.get', 'OperationStat.get'],
plan: ['Deposit.get', 'WellOperation.get'],
fact: ['Deposit.get', 'WellOperation.get'],
drillProcessFlow: ['Deposit.get', 'DrillFlowChart.get'],
params: ['Deposit.get', 'WellOperation.get', 'DrillParams.get'],
composite: {
wells: ['Deposit.get', 'OperationStat.get', 'WellComposite.get'],
sections: ['Deposit.get', 'OperationStat.get', 'WellComposite.get', 'DrillParams.get'],
}
},
telemetryAnalysis: {
depthToDay: ['Deposit.get', 'TelemetryAnalytics.get'],
depthToInterval: ['Deposit.get', 'TelemetryAnalytics.get'],
operationsSummary: ['Deposit.get', 'TelemetryAnalytics.get'],
operationsToInterval: ['Deposit.get'], // Не имеет собственных требований
},
measure: ['Deposit.get', 'Measure.get'],
drillingProgram: ['Deposit.get', 'Well.get', 'DrillingProgram.get'],
}
}
}
// Страницы Login и Register используют Auth.edit, однако не учитываются, так как считается, что их использует аноним
// Для всех страниц, на которых в PageHeader используется WellTreeSelector небходим доступ к Deposit.get