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

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

View File

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

View File

@ -14,7 +14,7 @@ export type PageHeaderProps = BasicProps & {
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}>
<Link to={'/'} style={{ height: headerHeight }}>
<Logo />

View File

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

View File

@ -1,6 +1,7 @@
import { createContext, useContext } from 'react'
import { createContext, useContext, useEffect } from 'react'
import { UserTokenDto, WellDto } from '@api'
import { LayoutPortalProps } from './components/LayoutPortal'
/** Контекст текущей скважины */
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 UserContext = createContext<UserTokenDto>({})
/** Контекст метода редактирования параметров заголовка и меню */
export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {})
/**
* Получить текущую скважину
@ -29,3 +32,18 @@ export const useRootPath = () => useContext(RootPathContext)
* @returns Текущий пользователь, либо `null`
*/
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 { lazy, memo, Suspense, useMemo } from 'react'
import { lazy, memo, useMemo } from 'react'
import { RootPathContext, useRootPath } from '@asb/context'
import { LayoutPortal } from '@components/LayoutPortal'
import SuspenseFallback from '@components/SuspenseFallback'
import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context'
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
import AdminNavigationMenu from './AdminNavigationMenu'
@ -21,14 +19,20 @@ const TelemetryViewer = lazy(() => import('./Telemetry/TelemetryViewer'))
const TelemetryMerger = lazy(() => import('./Telemetry/TelemetryMerger'))
const VisitLog = lazy(() => import('./VisitLog'))
const layoutProps = {
sider: <AdminNavigationMenu />,
title: 'Администраторская панель',
isAdmin: true,
}
const AdminPanel = memo(() => {
const root = useRootPath()
const rootPath = useMemo(() => `${root}/admin`, [root])
useLayoutProps(layoutProps)
return (
<RootPathContext.Provider value={rootPath}>
<LayoutPortal isAdmin title={'Администраторская панель'} sider={<AdminNavigationMenu />}>
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
<Routes>
<Route index element={<Navigate to={'visit_log'} replace />} />
<Route path={'*'} element={<NoAccessComponent />} />
@ -48,8 +52,6 @@ const AdminPanel = memo(() => {
</Route>
<Route path={'visit_log'} element={<VisitLog />} />
</Routes>
</Suspense>
</LayoutPortal>
</RootPathContext.Provider>
)
})

View File

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

View File

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

View File

@ -3,8 +3,8 @@ import { useState, useEffect, memo, useMemo } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { Popover, Badge } from 'antd'
import { useLayoutProps } from '@asb/context'
import { PointerIcon } from '@components/icons'
import { LayoutPortal } from '@components/LayoutPortal'
import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { arrayOrDefault, limitValue, wrapPrivateComponent } from '@utils'
@ -47,6 +47,8 @@ const Deposit = memo(() => {
const [showLoader, setShowLoader] = useState(false)
const [viewParams, setViewParams] = useState(defaultViewParams)
const setLayoutProps = useLayoutProps()
const location = useLocation()
const selectorProps = useMemo(() => {
@ -58,6 +60,13 @@ const Deposit = memo(() => {
}
}, [location.pathname])
useEffect(() => setLayoutProps({
sheet: false,
showSelector: true,
selectorProps,
title: 'Месторождение',
}), [setLayoutProps, selectorProps])
useEffect(() => {
invokeWebApiWrapperAsync(
async () => {
@ -72,7 +81,6 @@ const Deposit = memo(() => {
}, [])
return (
<LayoutPortal noSheet showSelector selectorProps={selectorProps} title={'Месторождение'} sider={true}>
<LoaderPortal show={showLoader}>
<div className={'h-100vh'} style={{ overflow: 'hidden' }}>
<Map {...viewParams}>
@ -102,7 +110,6 @@ const Deposit = memo(() => {
</Map>
</div>
</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 { WellContext, RootPathContext, useRootPath } from '@asb/context'
import { LayoutPortal } from '@components/LayoutPortal'
import SuspenseFallback from '@components/SuspenseFallback'
import { WellContext, RootPathContext, useRootPath, useLayoutProps } from '@asb/context'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
import { WellService } from '@api'
import NavigationMenu from '../NavigationMenu'
import NavigationMenu from './NavigationMenu'
import '@styles/index.css'
@ -49,16 +47,9 @@ const Well = memo(() => {
const root = useRootPath()
const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell])
useEffect(() => {
invokeWebApiWrapperAsync(
async () => {
const well = await WellService.get(idWell)
setWell(well ?? { id: idWell })
},
undefined,
'Не удалось получить данные по скважине'
)
}, [idWell])
const setLayoutProps = useLayoutProps()
useEffect(() => console.log(well), [well])
const updateWell = useCallback((data) => invokeWebApiWrapperAsync(
async () => {
@ -71,11 +62,22 @@ const Well = memo(() => {
{ actionName: 'Изменение данных скважины', 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 (
<RootPathContext.Provider value={rootPath}>
<WellContext.Provider value={[well, updateWell]}>
<LayoutPortal sider={<NavigationMenu />}>
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100%' }} />}>
<Routes>
<Route index element={<Navigate to={'telemetry'} replace />} />
<Route path={'*'} element={<NoAccessComponent />} />
@ -121,8 +123,6 @@ const Well = memo(() => {
<Route path={'drillingProgram'} element={<DrillingProgram />} />
<Route path={'well_case'} element={<WellCase />} />
</Routes>
</Suspense>
</LayoutPortal>
</WellContext.Provider>
</RootPathContext.Provider>
)