* Навигационное меню скважины перенесено в соответствующую директорию

* LayoutPortal переделан в обёртку для сокращения повторений
* Добавлен контекст и хук для обновления параметров LayoutPortal
This commit is contained in:
goodmice 2022-10-13 18:16:43 +05:00
parent fc8b351b7c
commit 718d32f2b9
No known key found for this signature in database
GPG Key ID: 63EA771203189CF1
11 changed files with 205 additions and 150 deletions

View File

@ -5,6 +5,7 @@ import { ConfigProvider } from 'antd'
import { RootPathContext } from '@asb/context' import { RootPathContext } from '@asb/context'
import { UserOutlet } from '@components/outlets' import { UserOutlet } from '@components/outlets'
import LayoutPortal from '@components/LayoutPortal'
import SuspenseFallback from '@components/SuspenseFallback' import SuspenseFallback from '@components/SuspenseFallback'
import { getUserToken, NoAccessComponent } from '@utils' import { getUserToken, NoAccessComponent } from '@utils'
import { OpenAPI } from '@api' import { OpenAPI } from '@api'
@ -40,15 +41,18 @@ export const App = memo(() => (
{/* User pages */} {/* User pages */}
<Route element={<UserOutlet />}> <Route element={<UserOutlet />}>
<Route path={'/file_download/:idWell/:idFile/*'} element={<FileDownload />} />
<Route element={<LayoutPortal />}>
{/* Admin pages */} {/* Admin pages */}
<Route path={'/admin/*'} element={<AdminPanel />} /> <Route path={'/admin/*'} element={<AdminPanel />} />
{/* Client pages */} {/* Client pages */}
<Route path={'/file_download/:idWell/:idFile/*'} element={<FileDownload />} />
<Route path={'/deposit/*'} element={<Deposit />} /> <Route path={'/deposit/*'} element={<Deposit />} />
<Route path={'/cluster/:idCluster'} element={<Cluster />} /> <Route path={'/cluster/:idCluster'} element={<Cluster />} />
<Route path={'/well/:idWell/*'} element={<Well />} /> <Route path={'/well/:idWell/*'} element={<Well />} />
</Route> </Route>
</Route>
</Routes> </Routes>
</Router> </Router>
</Suspense> </Suspense>

View File

@ -1,7 +1,7 @@
import { Button, Layout, LayoutProps, Menu, SiderProps } from 'antd' import { Button, Layout, LayoutProps, Menu, SiderProps } from 'antd'
import { HTMLProps, Key, memo, ReactNode, useEffect, useMemo, useState } from 'react' import { Key, memo, ReactNode, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
import { ItemType } from 'antd/lib/menu/hooks/useItems' import { ItemType } from 'antd/lib/menu/hooks/useItems'
import { Link } from 'react-router-dom' import { Link, Outlet } from 'react-router-dom'
import { import {
ApartmentOutlined, ApartmentOutlined,
CodeOutlined, CodeOutlined,
@ -11,32 +11,47 @@ import {
UserOutlined, UserOutlined,
} from '@ant-design/icons' } from '@ant-design/icons'
import { LayoutPropsContext } from '@asb/context'
import PageHeader from '@components/PageHeader' import PageHeader from '@components/PageHeader'
import { UserMenu, UserMenuProps } from '@components/UserMenu' import { UserMenu, UserMenuProps } from '@components/UserMenu'
import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector' import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector'
import { isURLAvailable, wrapPrivateComponent } from '@utils' import { isURLAvailable, wrapPrivateComponent } from '@utils'
import SuspenseFallback from './SuspenseFallback'
import '@styles/layout.less' import '@styles/layout.less'
const { Content, Sider } = Layout const { Content, Sider } = Layout
export type LayoutPortalProps = HTMLProps<HTMLDivElement> & { export type LayoutPortalProps = LayoutProps & {
title?: ReactNode title?: ReactNode
noSheet?: boolean sheet?: boolean
showSelector?: boolean showSelector?: boolean
selectorProps?: WellTreeSelectorProps selectorProps?: WellTreeSelectorProps
sider?: boolean | JSX.Element sider?: boolean | JSX.Element
siderProps?: SiderProps & { userMenuProps?: UserMenuProps } siderProps?: SiderProps & { userMenuProps?: UserMenuProps }
isAdmin?: boolean isAdmin?: boolean
fallback?: JSX.Element
}
const defaultProps: LayoutPortalProps = {
title: 'Единая цифровая платформа',
sider: true,
sheet: true,
} }
const makeItem = (title: string, key: Key, icon: JSX.Element, label?: ReactNode, onClick?: () => void) => ({ icon, key, title, label: label ?? title, onClick }) const 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 _LayoutPortal = memo(() => {
const [menuCollapsed, setMenuCollapsed] = useState<boolean>(true) const [menuCollapsed, setMenuCollapsed] = useState<boolean>(true)
const [wellsTreeOpen, setWellsTreeOpen] = useState<boolean>(false) const [wellsTreeOpen, setWellsTreeOpen] = useState<boolean>(false)
const [userMenuOpen, setUserMenuOpen] = useState<boolean>(false) const [userMenuOpen, setUserMenuOpen] = useState<boolean>(false)
const [currentWell, setCurrentWell] = useState<string>('') const [currentWell, setCurrentWell] = useState<string>('')
const [props, setProps] = useState<LayoutPortalProps>(defaultProps)
const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, ...other } = useMemo(() => props, [props])
const setLayoutProps = useCallback((props: LayoutPortalProps) => setProps({ ...defaultProps, ...props}), [])
useEffect(() => { useEffect(() => {
if (typeof showSelector === 'boolean') if (typeof showSelector === 'boolean')
@ -64,7 +79,6 @@ const _LayoutPortal = memo<LayoutPortalProps>(({ isAdmin, title, noSheet, showSe
<Menu <Menu
mode={'inline'} mode={'inline'}
items={menuItems} items={menuItems}
inlineCollapsed
theme={'dark'} theme={'dark'}
selectable={false} selectable={false}
/> />
@ -95,8 +109,12 @@ const _LayoutPortal = memo<LayoutPortalProps>(({ isAdmin, title, noSheet, showSe
</> </>
)} )}
</PageHeader> </PageHeader>
<Content> <Content {...other} className={`${sheet ? 'site-layout-background sheet' : ''} ${other.className ?? ''}`}>
<div {...props} className={`${noSheet ? '' : 'site-layout-background sheet'} ${props.className}`} /> <LayoutPropsContext.Provider value={setLayoutProps}>
<Suspense fallback={fallback ?? <SuspenseFallback style={{ minHeight: '100%' }} />}>
<Outlet />
</Suspense>
</LayoutPropsContext.Provider>
</Content> </Content>
</Layout> </Layout>
</Layout> </Layout>

View File

@ -14,7 +14,7 @@ export type PageHeaderProps = BasicProps & {
children?: React.ReactNode children?: React.ReactNode
} }
export const PageHeader: React.FC<PageHeaderProps> = memo(({ title = 'Мониторинг', children, ...other }) => ( export const PageHeader: React.FC<PageHeaderProps> = memo(({ title, children, ...other }) => (
<Layout.Header className={'header'} {...other}> <Layout.Header className={'header'} {...other}>
<Link to={'/'} style={{ height: headerHeight }}> <Link to={'/'} style={{ height: headerHeight }}>
<Logo /> <Logo />

View File

@ -4,7 +4,6 @@ import { Link, useLocation } from 'react-router-dom'
import { join } from 'path' import { join } from 'path'
import { Menu, MenuProps } from 'antd' import { Menu, MenuProps } from 'antd'
import { useWell } from '@asb/context'
import { hasPermission, Permission } from '@utils' import { hasPermission, Permission } from '@utils'
type PrivateWellMenuItem = { type PrivateWellMenuItem = {
@ -71,16 +70,15 @@ export const makeItem = (
}) })
export type PrivateWellMenuProps = MenuProps & { export type PrivateWellMenuProps = MenuProps & {
idWell?: number
items: PrivateWellMenuItem[] items: PrivateWellMenuItem[]
rootPath?: string rootPath?: string
} }
export const PrivateWellMenu = memo<PrivateWellMenuProps>(({ items, rootPath = '/', ...other }) => { export const PrivateWellMenu = memo<PrivateWellMenuProps>(({ idWell, items, rootPath = '/', ...other }) => {
const [well] = useWell()
const location = useLocation() const location = useLocation()
const menuItems = useMemo(() => makeItemList(items, rootPath, well.id), [items, rootPath, well.id]) const menuItems = useMemo(() => makeItemList(items, rootPath, idWell), [items, rootPath, idWell])
const tabKeys = useMemo(() => { const tabKeys = useMemo(() => {
const out = [] const out = []

View File

@ -1,6 +1,7 @@
import { createContext, useContext } from 'react' import { createContext, useContext, useEffect } from 'react'
import { UserTokenDto, WellDto } from '@api' import { UserTokenDto, WellDto } from '@api'
import { LayoutPortalProps } from './components/LayoutPortal'
/** Контекст текущей скважины */ /** Контекст текущей скважины */
export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}]) export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}])
@ -8,6 +9,8 @@ export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}
export const RootPathContext = createContext<string>('/') export const RootPathContext = createContext<string>('/')
/** Контекст текущего пользователя */ /** Контекст текущего пользователя */
export const UserContext = createContext<UserTokenDto>({}) export const UserContext = createContext<UserTokenDto>({})
/** Контекст метода редактирования параметров заголовка и меню */
export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {})
/** /**
* Получить текущую скважину * Получить текущую скважину
@ -29,3 +32,18 @@ export const useRootPath = () => useContext(RootPathContext)
* @returns Текущий пользователь, либо `null` * @returns Текущий пользователь, либо `null`
*/ */
export const useUser = () => useContext(UserContext) export const useUser = () => useContext(UserContext)
/**
* Получить метод задания параметров заголовка и меню
*
* @returns Получить метод задания параметров заголовка и меню
*/
export const useLayoutProps = (props?: LayoutPortalProps) => {
const setLayoutProps = useContext(LayoutPropsContext)
useEffect(() => {
if (props) setLayoutProps(props)
}, [setLayoutProps, props])
return setLayoutProps
}

View File

@ -1,9 +1,7 @@
import { Navigate, Route, Routes } from 'react-router-dom' import { Navigate, Route, Routes } from 'react-router-dom'
import { lazy, memo, Suspense, useMemo } from 'react' import { lazy, memo, useMemo } from 'react'
import { RootPathContext, useRootPath } from '@asb/context' import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context'
import { LayoutPortal } from '@components/LayoutPortal'
import SuspenseFallback from '@components/SuspenseFallback'
import { NoAccessComponent, wrapPrivateComponent } from '@utils' import { NoAccessComponent, wrapPrivateComponent } from '@utils'
import AdminNavigationMenu from './AdminNavigationMenu' import AdminNavigationMenu from './AdminNavigationMenu'
@ -21,14 +19,20 @@ const TelemetryViewer = lazy(() => import('./Telemetry/TelemetryViewer'))
const TelemetryMerger = lazy(() => import('./Telemetry/TelemetryMerger')) const TelemetryMerger = lazy(() => import('./Telemetry/TelemetryMerger'))
const VisitLog = lazy(() => import('./VisitLog')) const VisitLog = lazy(() => import('./VisitLog'))
const layoutProps = {
sider: <AdminNavigationMenu />,
title: 'Администраторская панель',
isAdmin: true,
}
const AdminPanel = memo(() => { const AdminPanel = memo(() => {
const root = useRootPath() const root = useRootPath()
const rootPath = useMemo(() => `${root}/admin`, [root]) const rootPath = useMemo(() => `${root}/admin`, [root])
useLayoutProps(layoutProps)
return ( return (
<RootPathContext.Provider value={rootPath}> <RootPathContext.Provider value={rootPath}>
<LayoutPortal isAdmin title={'Администраторская панель'} sider={<AdminNavigationMenu />}>
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
<Routes> <Routes>
<Route index element={<Navigate to={'visit_log'} replace />} /> <Route index element={<Navigate to={'visit_log'} replace />} />
<Route path={'*'} element={<NoAccessComponent />} /> <Route path={'*'} element={<NoAccessComponent />} />
@ -48,8 +52,6 @@ const AdminPanel = memo(() => {
</Route> </Route>
<Route path={'visit_log'} element={<VisitLog />} /> <Route path={'visit_log'} element={<VisitLog />} />
</Routes> </Routes>
</Suspense>
</LayoutPortal>
</RootPathContext.Provider> </RootPathContext.Provider>
) )
}) })

View File

@ -1,5 +1,5 @@
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import { useState, useEffect, memo, useMemo } from 'react' import { useState, useEffect, memo, useMemo, lazy, Suspense } from 'react'
import { Button, Modal } from 'antd' import { Button, Modal } from 'antd'
import { LineChartOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons' import { LineChartOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons'
@ -15,6 +15,7 @@ import {
} from '@components/Table' } from '@components/Table'
import LoaderPortal from '@components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import PointerIcon from '@components/icons/PointerIcon' import PointerIcon from '@components/icons/PointerIcon'
import SuspenseFallback from '@components/SuspenseFallback'
import { invokeWebApiWrapperAsync } from '@components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { import {
getOperations, getOperations,
@ -24,8 +25,9 @@ import {
wrapPrivateComponent wrapPrivateComponent
} from '@utils' } from '@utils'
import Tvd from '@pages/Well/WellOperations/Tvd' const Tvd = lazy(() => import('@pages/Well/WellOperations/Tvd'))
import CompaniesTable from '@pages/Cluster/CompaniesTable'
import CompaniesTable from './CompaniesTable'
import WellOperationsTable from './WellOperationsTable' import WellOperationsTable from './WellOperationsTable'
const filtersMinMax = [ const filtersMinMax = [
@ -182,7 +184,9 @@ const ClusterWells = memo(({ statsWells }) => {
width={1500} width={1500}
footer={null} footer={null}
> >
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
<Tvd style={{ minHeight: '600px' }} well={selectedWell} /> <Tvd style={{ minHeight: '600px' }} well={selectedWell} />
</Suspense>
</Modal> </Modal>
<Modal <Modal

View File

@ -1,7 +1,7 @@
import { useState, useEffect, memo } from 'react' import { useState, useEffect, memo } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { LayoutPortal } from '@components/LayoutPortal' import { useLayoutProps } from '@asb/context'
import LoaderPortal from '@components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { arrayOrDefault, wrapPrivateComponent } from '@utils' import { arrayOrDefault, wrapPrivateComponent } from '@utils'
@ -9,11 +9,17 @@ import { OperationStatService } from '@api'
import ClusterWells from './ClusterWells' import ClusterWells from './ClusterWells'
const layoutProps = {
title: 'Анализ скважин куста'
}
const Cluster = memo(() => { const Cluster = memo(() => {
const { idCluster } = useParams() const { idCluster } = useParams()
const [data, setData] = useState([]) const [data, setData] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
useLayoutProps(layoutProps)
useEffect(() => { useEffect(() => {
invokeWebApiWrapperAsync( invokeWebApiWrapperAsync(
async () => { async () => {
@ -27,11 +33,9 @@ const Cluster = memo(() => {
}, [idCluster]) }, [idCluster])
return ( return (
<LayoutPortal title={'Анализ скважин куста'} sider={true}>
<LoaderPortal show={showLoader}> <LoaderPortal show={showLoader}>
<ClusterWells statsWells={data} /> <ClusterWells statsWells={data} />
</LoaderPortal> </LoaderPortal>
</LayoutPortal>
) )
}) })

View File

@ -3,8 +3,8 @@ import { useState, useEffect, memo, useMemo } from 'react'
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import { Popover, Badge } from 'antd' import { Popover, Badge } from 'antd'
import { useLayoutProps } from '@asb/context'
import { PointerIcon } from '@components/icons' import { PointerIcon } from '@components/icons'
import { LayoutPortal } from '@components/LayoutPortal'
import LoaderPortal from '@components/LoaderPortal' import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { arrayOrDefault, limitValue, wrapPrivateComponent } from '@utils' import { arrayOrDefault, limitValue, wrapPrivateComponent } from '@utils'
@ -47,6 +47,8 @@ const Deposit = memo(() => {
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
const [viewParams, setViewParams] = useState(defaultViewParams) const [viewParams, setViewParams] = useState(defaultViewParams)
const setLayoutProps = useLayoutProps()
const location = useLocation() const location = useLocation()
const selectorProps = useMemo(() => { const selectorProps = useMemo(() => {
@ -58,6 +60,13 @@ const Deposit = memo(() => {
} }
}, [location.pathname]) }, [location.pathname])
useEffect(() => setLayoutProps({
sheet: false,
showSelector: true,
selectorProps,
title: 'Месторождение',
}), [setLayoutProps, selectorProps])
useEffect(() => { useEffect(() => {
invokeWebApiWrapperAsync( invokeWebApiWrapperAsync(
async () => { async () => {
@ -72,7 +81,6 @@ const Deposit = memo(() => {
}, []) }, [])
return ( return (
<LayoutPortal noSheet showSelector selectorProps={selectorProps} title={'Месторождение'} sider={true}>
<LoaderPortal show={showLoader}> <LoaderPortal show={showLoader}>
<div className={'h-100vh'} style={{ overflow: 'hidden' }}> <div className={'h-100vh'} style={{ overflow: 'hidden' }}>
<Map {...viewParams}> <Map {...viewParams}>
@ -102,7 +110,6 @@ const Deposit = memo(() => {
</Map> </Map>
</div> </div>
</LoaderPortal> </LoaderPortal>
</LayoutPortal>
) )
}) })

View File

@ -1,14 +1,12 @@
import { lazy, memo, Suspense, useCallback, useEffect, useMemo, useState } from 'react' import { lazy, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { Navigate, Route, Routes, useParams } from 'react-router-dom' import { Navigate, Route, Routes, useParams } from 'react-router-dom'
import { WellContext, RootPathContext, useRootPath } from '@asb/context' import { WellContext, RootPathContext, useRootPath, useLayoutProps } from '@asb/context'
import { LayoutPortal } from '@components/LayoutPortal'
import SuspenseFallback from '@components/SuspenseFallback'
import { invokeWebApiWrapperAsync } from '@components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { NoAccessComponent, wrapPrivateComponent } from '@utils' import { NoAccessComponent, wrapPrivateComponent } from '@utils'
import { WellService } from '@api' import { WellService } from '@api'
import NavigationMenu from '../NavigationMenu' import NavigationMenu from './NavigationMenu'
import '@styles/index.css' import '@styles/index.css'
@ -49,16 +47,9 @@ const Well = memo(() => {
const root = useRootPath() const root = useRootPath()
const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell]) const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell])
useEffect(() => { const setLayoutProps = useLayoutProps()
invokeWebApiWrapperAsync(
async () => { useEffect(() => console.log(well), [well])
const well = await WellService.get(idWell)
setWell(well ?? { id: idWell })
},
undefined,
'Не удалось получить данные по скважине'
)
}, [idWell])
const updateWell = useCallback((data) => invokeWebApiWrapperAsync( const updateWell = useCallback((data) => invokeWebApiWrapperAsync(
async () => { async () => {
@ -71,11 +62,22 @@ const Well = memo(() => {
{ actionName: 'Изменение данных скважины', well } { actionName: 'Изменение данных скважины', well }
), [well]) ), [well])
useEffect(() => {
invokeWebApiWrapperAsync(
async () => {
const well = await WellService.get(idWell)
setWell(well ?? { id: idWell })
},
undefined,
'Не удалось получить данные по скважине'
)
}, [idWell])
useEffect(() => setLayoutProps({ sider: <NavigationMenu idWell={well.id} /> }), [well, setLayoutProps])
return ( return (
<RootPathContext.Provider value={rootPath}> <RootPathContext.Provider value={rootPath}>
<WellContext.Provider value={[well, updateWell]}> <WellContext.Provider value={[well, updateWell]}>
<LayoutPortal sider={<NavigationMenu />}>
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
<Routes> <Routes>
<Route index element={<Navigate to={'telemetry'} replace />} /> <Route index element={<Navigate to={'telemetry'} replace />} />
<Route path={'*'} element={<NoAccessComponent />} /> <Route path={'*'} element={<NoAccessComponent />} />
@ -121,8 +123,6 @@ const Well = memo(() => {
<Route path={'drillingProgram'} element={<DrillingProgram />} /> <Route path={'drillingProgram'} element={<DrillingProgram />} />
<Route path={'well_case'} element={<WellCase />} /> <Route path={'well_case'} element={<WellCase />} />
</Routes> </Routes>
</Suspense>
</LayoutPortal>
</WellContext.Provider> </WellContext.Provider>
</RootPathContext.Provider> </RootPathContext.Provider>
) )