forked from ddrilling/asb_cloud_front
Merge branch 'dev' into feature/merging-telemetry-view-and-archive
This commit is contained in:
commit
ec2513b4a0
53
src/App.tsx
53
src/App.tsx
@ -1,18 +1,16 @@
|
|||||||
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom'
|
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { lazy, memo, Suspense } from 'react'
|
import { lazy, memo, Suspense } from 'react'
|
||||||
import locale from 'antd/lib/locale/ru_RU'
|
|
||||||
import { ConfigProvider } from 'antd'
|
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { RootPathContext } from '@asb/context'
|
||||||
import { UserOutlet } from '@components/outlets'
|
|
||||||
import LayoutPortal from '@components/LayoutPortal'
|
|
||||||
import SuspenseFallback from '@components/SuspenseFallback'
|
import SuspenseFallback from '@components/SuspenseFallback'
|
||||||
import { getUser, NoAccessComponent } from '@utils'
|
import { NoAccessComponent } from '@utils'
|
||||||
import { OpenAPI } from '@api'
|
|
||||||
|
|
||||||
import '@styles/include/antd_theme.less'
|
|
||||||
import '@styles/App.less'
|
import '@styles/App.less'
|
||||||
|
|
||||||
|
const UserOutlet = lazy(() => import('@components/outlets/UserOutlet'))
|
||||||
|
const DepositsOutlet = lazy(() => import('@components/outlets/DepositsOutlet'))
|
||||||
|
const LayoutPortal = lazy(() => import('@components/LayoutPortal'))
|
||||||
|
|
||||||
const Login = lazy(() => import('@pages/public/Login'))
|
const Login = lazy(() => import('@pages/public/Login'))
|
||||||
const Register = lazy(() => import('@pages/public/Register'))
|
const Register = lazy(() => import('@pages/public/Register'))
|
||||||
const FileDownload = lazy(() => import('@pages/FileDownload'))
|
const FileDownload = lazy(() => import('@pages/FileDownload'))
|
||||||
@ -22,28 +20,23 @@ const Deposit = lazy(() => import('@pages/Deposit'))
|
|||||||
const Cluster = lazy(() => import('@pages/Cluster'))
|
const Cluster = lazy(() => import('@pages/Cluster'))
|
||||||
const Well = lazy(() => import('@pages/Well'))
|
const Well = lazy(() => import('@pages/Well'))
|
||||||
|
|
||||||
// OpenAPI.BASE = 'http://localhost:3000'
|
|
||||||
// TODO: Удалить взятие из 'token' в следующем релизе, вставлено для совместимости
|
|
||||||
OpenAPI.TOKEN = async () => getUser().token || localStorage.getItem('token') || ''
|
|
||||||
OpenAPI.HEADERS = { 'Content-Type': 'application/json' }
|
|
||||||
|
|
||||||
export const App = memo(() => (
|
export const App = memo(() => (
|
||||||
<ConfigProvider locale={locale}>
|
<RootPathContext.Provider value={''}>
|
||||||
<RootPathContext.Provider value={''}>
|
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100vh' }} />}>
|
||||||
<Suspense fallback={<SuspenseFallback style={{ minHeight: '100vh' }} />}>
|
<Router>
|
||||||
<Router>
|
<Routes>
|
||||||
<Routes>
|
<Route index element={<Navigate to={'deposit'} replace />} />
|
||||||
<Route index element={<Navigate to={'deposit'} replace />} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
<Route path={'*'} element={<NoAccessComponent />} />
|
|
||||||
|
|
||||||
{/* Public pages */}
|
{/* Public pages */}
|
||||||
<Route path={'/login'} element={<Login />} />
|
<Route path={'/login'} element={<Login />} />
|
||||||
<Route path={'/register'} element={<Register />} />
|
<Route path={'/register'} element={<Register />} />
|
||||||
|
|
||||||
{/* User pages */}
|
{/* User pages */}
|
||||||
<Route element={<UserOutlet />}>
|
<Route element={<UserOutlet />}>
|
||||||
<Route path={'/file_download/:idFile/*'} element={<FileDownload />} />
|
<Route path={'/file_download/:idFile/*'} element={<FileDownload />} />
|
||||||
|
|
||||||
|
<Route element={<DepositsOutlet />}>
|
||||||
<Route element={<LayoutPortal />}>
|
<Route element={<LayoutPortal />}>
|
||||||
{/* Admin pages */}
|
{/* Admin pages */}
|
||||||
<Route path={'/admin/*'} element={<AdminPanel />} />
|
<Route path={'/admin/*'} element={<AdminPanel />} />
|
||||||
@ -54,11 +47,11 @@ export const App = memo(() => (
|
|||||||
<Route path={'/well/:idWell/*'} element={<Well />} />
|
<Route path={'/well/:idWell/*'} element={<Well />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Route>
|
||||||
</Router>
|
</Routes>
|
||||||
</Suspense>
|
</Router>
|
||||||
</RootPathContext.Provider>
|
</Suspense>
|
||||||
</ConfigProvider>
|
</RootPathContext.Provider>
|
||||||
))
|
))
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
@ -22,7 +22,7 @@ export const Grid = memo<ComponentProps>(({ children, style, ...other }) => (
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|
||||||
export const GridItem = memo<GridItemProps>(({ children, row, col, rowSpan, colSpan, style, ...other }) => {
|
export const GridItem = memo<GridItemProps>(({ children, row, col, rowSpan, colSpan, style, className, ...other }) => {
|
||||||
const localRow = +row
|
const localRow = +row
|
||||||
const localCol = +col
|
const localCol = +col
|
||||||
const localColSpan = colSpan ? colSpan - 1 : 0
|
const localColSpan = colSpan ? colSpan - 1 : 0
|
||||||
@ -32,12 +32,11 @@ export const GridItem = memo<GridItemProps>(({ children, row, col, rowSpan, colS
|
|||||||
gridColumnEnd: localCol + localColSpan,
|
gridColumnEnd: localCol + localColSpan,
|
||||||
gridRowStart: localRow,
|
gridRowStart: localRow,
|
||||||
gridRowEnd: localRow + localRowSpan,
|
gridRowEnd: localRow + localRowSpan,
|
||||||
padding: '4px',
|
|
||||||
...style,
|
...style,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={gridItemStyle} {...other}>
|
<div className={`asb-grid-item ${className || ''}`} style={gridItemStyle} {...other}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -6,11 +6,12 @@ type LoaderPortalProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
show?: boolean,
|
show?: boolean,
|
||||||
fade?: boolean,
|
fade?: boolean,
|
||||||
spinnerProps?: HTMLAttributes<HTMLDivElement>,
|
spinnerProps?: HTMLAttributes<HTMLDivElement>,
|
||||||
|
fillContent?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LoaderPortal: React.FC<LoaderPortalProps> = ({ className = '', show, fade = true, children, spinnerProps, ...other }) => (
|
export const LoaderPortal: React.FC<LoaderPortalProps> = ({ className = '', show, fade = true, children, spinnerProps, fillContent, ...other }) => (
|
||||||
<div className={`loader-container ${className}`} {...other}>
|
<div className={`loader-container ${className}`} {...other}>
|
||||||
<div className={'loader-content'}>{children}</div>
|
<div className={`loader-content${fillContent ? ' loader-content-fill' : ''}`}>{children}</div>
|
||||||
{show && fade && <div className={'loader-fade'}/>}
|
{show && fade && <div className={'loader-fade'}/>}
|
||||||
{show && <div className={'loader-overlay'}><Loader {...spinnerProps} /></div>}
|
{show && <div className={'loader-overlay'}><Loader {...spinnerProps} /></div>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -221,9 +221,7 @@ export const EditableTable = memo(({
|
|||||||
|
|
||||||
const mergedColumns = useMemo(() => [...columns.map(handleColumn), operationColumn], [columns, handleColumn, operationColumn])
|
const mergedColumns = useMemo(() => [...columns.map(handleColumn), operationColumn], [columns, handleColumn, operationColumn])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => setData(tryAddKeys(dataSource)), [dataSource])
|
||||||
setData(tryAddKeys(dataSource))
|
|
||||||
}, [dataSource])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form form={form}>
|
<Form form={form}>
|
||||||
|
@ -193,6 +193,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
|||||||
methods,
|
methods,
|
||||||
|
|
||||||
className = '',
|
className = '',
|
||||||
|
style,
|
||||||
...other
|
...other
|
||||||
}: D3MonitoringChartsProps<DataType>) => {
|
}: D3MonitoringChartsProps<DataType>) => {
|
||||||
const [datasets, setDatasets, resetDatasets] = useUserSettings(chartName, datasetGroups)
|
const [datasets, setDatasets, resetDatasets] = useUserSettings(chartName, datasetGroups)
|
||||||
@ -351,10 +352,10 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
|||||||
x: getByAccessor(dataset.xAxis?.accessor),
|
x: getByAccessor(dataset.xAxis?.accessor),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (newChart.type === 'line')
|
if (newChart.type === 'line')
|
||||||
newChart.optimization = false
|
newChart.optimization = false
|
||||||
|
|
||||||
// Если у графика нет группы создаём её
|
// Если у графика нет группы создаём её
|
||||||
if (newChart().empty())
|
if (newChart().empty())
|
||||||
group().append('g')
|
group().append('g')
|
||||||
@ -496,12 +497,12 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chart.point)
|
if (chart.point)
|
||||||
renderPoint<DataType>(xAxis, yAxis, chart, chartData, true)
|
renderPoint<DataType>(xAxis, yAxis, chart, chartData, true)
|
||||||
|
|
||||||
if (dash) chart().attr('stroke-dasharray', dash)
|
if (dash) chart().attr('stroke-dasharray', dash)
|
||||||
|
|
||||||
chart.afterDraw?.(chart)
|
chart.afterDraw?.(chart)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -513,6 +514,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
|||||||
style={{
|
style={{
|
||||||
width: givenWidth,
|
width: givenWidth,
|
||||||
height: givenHeight,
|
height: givenHeight,
|
||||||
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
35
src/components/outlets/DepositsOutlet.tsx
Normal file
35
src/components/outlets/DepositsOutlet.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
import { Outlet } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { DepositsContext } from '@asb/context'
|
||||||
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { DepositDto, DepositService } from '@api'
|
||||||
|
import { arrayOrDefault } from '@utils'
|
||||||
|
|
||||||
|
export const DepositsOutlet = memo(() => {
|
||||||
|
const [deposits, setDeposits] = useState<DepositDto[]>([])
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const deposits = await DepositService.getDeposits()
|
||||||
|
setDeposits(arrayOrDefault(deposits))
|
||||||
|
},
|
||||||
|
setIsLoading,
|
||||||
|
`Не удалось загрузить список кустов`,
|
||||||
|
{ actionName: 'Получить список кустов' }
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DepositsContext.Provider value={deposits}>
|
||||||
|
<LoaderPortal show={isLoading}>
|
||||||
|
<Outlet />
|
||||||
|
</LoaderPortal>
|
||||||
|
</DepositsContext.Provider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default DepositsOutlet
|
@ -1 +1,2 @@
|
|||||||
|
export * from './DepositsOutlet'
|
||||||
export * from './UserOutlet'
|
export * from './UserOutlet'
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Tag, TreeSelect } from 'antd'
|
import { Tag, TreeSelect } from 'antd'
|
||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { useDeposits } from '@asb/context'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { hasPermission } from '@utils'
|
import { hasPermission } from '@utils'
|
||||||
import { DepositService } from '@api'
|
|
||||||
|
|
||||||
export const getTreeData = async () => {
|
export const getTreeData = async (deposits) => {
|
||||||
const deposits = await DepositService.getDeposits()
|
|
||||||
const wellsTree = deposits.map((deposit, dIdx) => ({
|
const wellsTree = deposits.map((deposit, dIdx) => ({
|
||||||
title: deposit.caption,
|
title: deposit.caption,
|
||||||
key: `0-${dIdx}`,
|
key: `0-${dIdx}`,
|
||||||
@ -40,10 +39,12 @@ export const WellSelector = memo(({ value, onChange, treeData, treeLabels, ...ot
|
|||||||
const [wellsTree, setWellsTree] = useState([])
|
const [wellsTree, setWellsTree] = useState([])
|
||||||
const [wellLabels, setWellLabels] = useState([])
|
const [wellLabels, setWellLabels] = useState([])
|
||||||
|
|
||||||
|
const deposits = useDeposits()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
invokeWebApiWrapperAsync(
|
invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const wellsTree = treeData ?? await getTreeData()
|
const wellsTree = treeData ?? await getTreeData(deposits)
|
||||||
const labels = treeLabels ?? getTreeLabels(wellsTree)
|
const labels = treeLabels ?? getTreeLabels(wellsTree)
|
||||||
setWellsTree(wellsTree)
|
setWellsTree(wellsTree)
|
||||||
setWellLabels(labels)
|
setWellLabels(labels)
|
||||||
@ -52,7 +53,7 @@ export const WellSelector = memo(({ value, onChange, treeData, treeLabels, ...ot
|
|||||||
'Не удалось загрузить список скважин',
|
'Не удалось загрузить список скважин',
|
||||||
{ actionName: 'Получение списка скважин' }
|
{ actionName: 'Получение списка скважин' }
|
||||||
)
|
)
|
||||||
}, [treeData, treeLabels])
|
}, [deposits, treeData, treeLabels])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Button, Drawer, Skeleton, Tree, TreeDataNode, TreeProps, Typography } from 'antd'
|
import { Drawer, Tree, TreeDataNode, TreeProps } from 'antd'
|
||||||
import { useState, useEffect, useCallback, memo, Key } from 'react'
|
import { useState, useEffect, useCallback, memo, Key, useMemo } from 'react'
|
||||||
import { useNavigate, useLocation } from 'react-router-dom'
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { useDeposits } from '@asb/context'
|
||||||
import { WellIcon, WellIconState } from '@components/icons'
|
import { WellIcon, WellIconState } from '@components/icons'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { DepositDto, WellDto } from '@api'
|
||||||
import { DepositService, DepositDto, WellDto } from '@api'
|
|
||||||
import { isRawDate } from '@utils'
|
import { isRawDate } from '@utils'
|
||||||
|
|
||||||
import { ReactComponent as DepositIcon } from '@images/DepositIcon.svg'
|
import { ReactComponent as DepositIcon } from '@images/DepositIcon.svg'
|
||||||
@ -91,62 +91,44 @@ const getExpandKeys = (treeData: TreeDataNode[], depositKeys?: Key[] | boolean):
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const makeWellsTreeData = (deposits: DepositDto[]): TreeDataNode[] => deposits.map(deposit =>({
|
||||||
|
title: deposit.caption,
|
||||||
|
key: `/deposit/${deposit.id}`,
|
||||||
|
value: `/deposit/${deposit.id}`,
|
||||||
|
icon: <DepositIcon width={24} height={24}/>,
|
||||||
|
children: deposit.clusters?.map(cluster => {
|
||||||
|
const wells = cluster.wells ? cluster.wells.slice() : []
|
||||||
|
wells.sort(sortWellsByActive)
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: cluster.caption,
|
||||||
|
key: `/cluster/${cluster.id}`,
|
||||||
|
value: `/cluster/${cluster.id}`,
|
||||||
|
icon: <ClusterIcon width={24} height={24}/>,
|
||||||
|
children: wells.map(well => ({
|
||||||
|
title: well.caption,
|
||||||
|
key: `/well/${well.id}`,
|
||||||
|
value: `/well/${well.id}`,
|
||||||
|
icon: <WellIcon
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
state={getWellState(well.idState)}
|
||||||
|
online={checkIsWellOnline(well.lastTelemetryDate)}
|
||||||
|
/>
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
export const WellTreeSelector = memo<WellTreeSelectorProps>(({ expand, current, onChange, onClose, open, ...other }) => {
|
export const WellTreeSelector = memo<WellTreeSelectorProps>(({ expand, current, onChange, onClose, open, ...other }) => {
|
||||||
const [wellsTree, setWellsTree] = useState<TreeDataNode[]>([])
|
|
||||||
const [showLoader, setShowLoader] = useState<boolean>(false)
|
|
||||||
const [expanded, setExpanded] = useState<Key[]>([])
|
const [expanded, setExpanded] = useState<Key[]>([])
|
||||||
const [selected, setSelected] = useState<Key[]>([])
|
const [selected, setSelected] = useState<Key[]>([])
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const deposits = useDeposits()
|
||||||
|
|
||||||
useEffect(() => {
|
const wellsTree = useMemo(() => makeWellsTreeData(deposits), [deposits])
|
||||||
if (current) setSelected([current])
|
|
||||||
}, [current])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setExpanded((prev) => expand ? getExpandKeys(wellsTree, expand) : prev)
|
|
||||||
}, [wellsTree, expand])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const deposits: Array<DepositDto> = await DepositService.getDeposits()
|
|
||||||
const wellsTree: TreeDataNode[] = deposits.map(deposit =>({
|
|
||||||
title: deposit.caption,
|
|
||||||
key: `/deposit/${deposit.id}`,
|
|
||||||
value: `/deposit/${deposit.id}`,
|
|
||||||
icon: <DepositIcon width={24} height={24}/>,
|
|
||||||
children: deposit.clusters?.map(cluster => {
|
|
||||||
const wells = cluster.wells ? cluster.wells.slice() : []
|
|
||||||
wells.sort(sortWellsByActive)
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: cluster.caption,
|
|
||||||
key: `/cluster/${cluster.id}`,
|
|
||||||
value: `/cluster/${cluster.id}`,
|
|
||||||
icon: <ClusterIcon width={24} height={24}/>,
|
|
||||||
children: wells.map(well => ({
|
|
||||||
title: well.caption,
|
|
||||||
key: `/well/${well.id}`,
|
|
||||||
value: `/well/${well.id}`,
|
|
||||||
icon: <WellIcon
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
state={getWellState(well.idState)}
|
|
||||||
online={checkIsWellOnline(well.lastTelemetryDate)}
|
|
||||||
/>
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
setWellsTree(wellsTree)
|
|
||||||
},
|
|
||||||
setShowLoader,
|
|
||||||
`Не удалось загрузить список скважин`,
|
|
||||||
{ actionName: 'Получить список скважин' }
|
|
||||||
)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onValueChange = useCallback((value?: string): void => {
|
const onValueChange = useCallback((value?: string): void => {
|
||||||
const key = getKeyByUrl(value)[0]
|
const key = getKeyByUrl(value)[0]
|
||||||
@ -169,21 +151,27 @@ export const WellTreeSelector = memo<WellTreeSelectorProps>(({ expand, current,
|
|||||||
navigate(newPath, { state: { from: location.pathname }})
|
navigate(newPath, { state: { from: location.pathname }})
|
||||||
}, [navigate, location])
|
}, [navigate, location])
|
||||||
|
|
||||||
useEffect(() => onValueChange(location.pathname), [onValueChange, location])
|
useEffect(() => {
|
||||||
|
if (current) setSelected([current])
|
||||||
|
}, [current])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpanded((prev) => expand ? getExpandKeys(wellsTree, expand) : prev)
|
||||||
|
}, [wellsTree, expand])
|
||||||
|
|
||||||
|
useEffect(() => onValueChange(location.pathname), [onValueChange, location.pathname])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={open} mask={false} onClose={onClose} title={'Список скважин'}>
|
<Drawer open={open} mask={false} onClose={onClose} title={'Список скважин'}>
|
||||||
<Skeleton active loading={showLoader}>
|
<Tree
|
||||||
<Tree
|
{...other}
|
||||||
{...other}
|
showIcon
|
||||||
showIcon
|
selectedKeys={selected}
|
||||||
selectedKeys={selected}
|
treeData={wellsTree}
|
||||||
treeData={wellsTree}
|
onSelect={onSelect}
|
||||||
onSelect={onSelect}
|
onExpand={setExpanded}
|
||||||
onExpand={setExpanded}
|
expandedKeys={expanded}
|
||||||
expandedKeys={expanded}
|
/>
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createContext, useContext, useEffect } from 'react'
|
import { createContext, useContext, useEffect } from 'react'
|
||||||
|
|
||||||
import { LayoutPortalProps } from '@components/LayoutPortal'
|
import { LayoutPortalProps } from '@components/LayoutPortal'
|
||||||
import { UserTokenDto, WellDto } from '@api'
|
import { DepositDto, UserTokenDto, WellDto } from '@api'
|
||||||
|
|
||||||
/** Контекст текущей скважины */
|
/** Контекст текущей скважины */
|
||||||
export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}])
|
export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}])
|
||||||
@ -13,6 +13,8 @@ export const UserContext = createContext<UserTokenDto>({})
|
|||||||
export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {})
|
export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {})
|
||||||
/** Контекст для блока справа от крошек на страницах скважин и админки */
|
/** Контекст для блока справа от крошек на страницах скважин и админки */
|
||||||
export const TopRightBlockContext = createContext<(block: JSX.Element) => void>(() => {})
|
export const TopRightBlockContext = createContext<(block: JSX.Element) => void>(() => {})
|
||||||
|
/** Контекст со списком месторождений */
|
||||||
|
export const DepositsContext = createContext<DepositDto[]>([])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить текущую скважину
|
* Получить текущую скважину
|
||||||
@ -29,19 +31,31 @@ export const useWell = () => useContext(WellContext)
|
|||||||
export const useRootPath = () => useContext(RootPathContext)
|
export const useRootPath = () => useContext(RootPathContext)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить текущего пользователя
|
* Получить текущего пользователя
|
||||||
*
|
*
|
||||||
* @returns Текущий пользователь, либо `null`
|
* @returns Текущий пользователь, либо `null`
|
||||||
*/
|
*/
|
||||||
export const useUser = () => useContext(UserContext)
|
export const useUser = () => useContext(UserContext)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить список скважин
|
||||||
|
*
|
||||||
|
* @returns Список скважин
|
||||||
|
*/
|
||||||
|
export const useDeposits = () => useContext(DepositsContext)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить метод задания элементов справа от крошек
|
||||||
|
*
|
||||||
|
* @returns Метод задания элементов справа от крошек
|
||||||
|
*/
|
||||||
export const useTopRightBlock = () => useContext(TopRightBlockContext)
|
export const useTopRightBlock = () => useContext(TopRightBlockContext)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить метод задания параметров заголовка и меню
|
* Получить метод задания параметров заголовка и меню
|
||||||
*
|
*
|
||||||
* @returns Получить метод задания параметров заголовка и меню
|
* @returns Получить метод задания параметров заголовка и меню
|
||||||
*/
|
*/
|
||||||
export const useLayoutProps = (props?: LayoutPortalProps) => {
|
export const useLayoutProps = (props?: LayoutPortalProps) => {
|
||||||
const setLayoutProps = useContext(LayoutPropsContext)
|
const setLayoutProps = useContext(LayoutPropsContext)
|
||||||
|
|
||||||
|
@ -1,15 +1,28 @@
|
|||||||
import React from 'react'
|
import locale from 'antd/lib/locale/ru_RU'
|
||||||
|
import { ConfigProvider } from 'antd'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { getUser } from '@utils'
|
||||||
|
import { OpenAPI } from '@api'
|
||||||
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
|
import '@styles/include/antd_theme.less'
|
||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
|
|
||||||
|
// OpenAPI.BASE = 'http://localhost:3000'
|
||||||
|
// TODO: Удалить взятие из 'token' в следующем релизе, вставлено для совместимости
|
||||||
|
OpenAPI.TOKEN = async () => getUser().token || localStorage.getItem('token') || ''
|
||||||
|
OpenAPI.HEADERS = { 'Content-Type': 'application/json' }
|
||||||
|
|
||||||
const container = document.getElementById('root') ?? document.body
|
const container = document.getElementById('root') ?? document.body
|
||||||
const root = createRoot(container)
|
const root = createRoot(container)
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<ConfigProvider locale={locale}>
|
||||||
|
<App />
|
||||||
|
</ConfigProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
)
|
)
|
||||||
|
@ -7,11 +7,11 @@ import {
|
|||||||
makeTextColumn,
|
makeTextColumn,
|
||||||
makeGroupColumn,
|
makeGroupColumn,
|
||||||
makeColumn,
|
makeColumn,
|
||||||
makeDateSorter,
|
|
||||||
makeNumericColumnPlanFact,
|
makeNumericColumnPlanFact,
|
||||||
Table,
|
Table,
|
||||||
makeNumericRender,
|
makeNumericRender,
|
||||||
makeNumericColumn,
|
makeNumericColumn,
|
||||||
|
makeDateColumn,
|
||||||
} 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'
|
||||||
@ -39,7 +39,6 @@ const filtersWellsType = []
|
|||||||
const DAY_IN_MS = 86_400_000
|
const DAY_IN_MS = 86_400_000
|
||||||
const ONLINE_DEADTIME = 600_000
|
const ONLINE_DEADTIME = 600_000
|
||||||
|
|
||||||
const getDate = (str) => isRawDate(str) ? new Date(str).toLocaleString() : '-'
|
|
||||||
const numericRender = makeNumericRender(1)
|
const numericRender = makeNumericRender(1)
|
||||||
|
|
||||||
const ClusterWells = memo(({ statsWells }) => {
|
const ClusterWells = memo(({ statsWells }) => {
|
||||||
@ -131,8 +130,8 @@ const ClusterWells = memo(({ statsWells }) => {
|
|||||||
),
|
),
|
||||||
makeTextColumn('Тип скв.', 'wellType', filtersWellsType, null, (text) => text ?? '-'),
|
makeTextColumn('Тип скв.', 'wellType', filtersWellsType, null, (text) => text ?? '-'),
|
||||||
makeGroupColumn('Фактические сроки', [
|
makeGroupColumn('Фактические сроки', [
|
||||||
makeColumn('начало', 'factStart', { sorter: makeDateSorter('factStart'), render: getDate }),
|
makeDateColumn('начало', 'factStart'),
|
||||||
makeColumn('окончание', 'factEnd', { sorter: makeDateSorter('factEnd'), render: getDate }),
|
makeDateColumn('окончание', 'factEnd'),
|
||||||
]),
|
]),
|
||||||
makeNumericColumnPlanFact('Продолжительность, сут', 'period', filtersMinMax, makeFilterMinMaxFunction, numericRender),
|
makeNumericColumnPlanFact('Продолжительность, сут', 'period', filtersMinMax, makeFilterMinMaxFunction, numericRender),
|
||||||
makeNumericColumnPlanFact('МСП, м/ч', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction, numericRender),
|
makeNumericColumnPlanFact('МСП, м/ч', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction, numericRender),
|
||||||
|
@ -1,120 +1,106 @@
|
|||||||
import { useState, useEffect, memo, useMemo } from 'react'
|
import { useEffect, memo, useMemo, useCallback } from 'react'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
import { Map, Overlay } from 'pigeon-maps'
|
import { Map, Overlay } from 'pigeon-maps'
|
||||||
import { Popover, Badge } from 'antd'
|
import { Popover, Badge } from 'antd'
|
||||||
|
|
||||||
import { useLayoutProps } from '@asb/context'
|
import { useDeposits, useLayoutProps } from '@asb/context'
|
||||||
import { PointerIcon } from '@components/icons'
|
import { PointerIcon } from '@components/icons'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { FastRunMenu } from '@components/FastRunMenu'
|
import { FastRunMenu } from '@components/FastRunMenu'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { limitValue, withPermissions } from '@utils'
|
||||||
import { arrayOrDefault, limitValue, withPermissions } from '@utils'
|
|
||||||
import { DepositService } from '@api'
|
|
||||||
|
|
||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
|
|
||||||
const defaultViewParams = { center: [60.81226, 70.0562], zoom: 5 }
|
|
||||||
|
|
||||||
const zoomLimit = limitValue(5, 15)
|
const zoomLimit = limitValue(5, 15)
|
||||||
|
|
||||||
const calcViewParams = (clusters) => {
|
const calcViewParams = (clusters) => {
|
||||||
if ((clusters?.length ?? 0) <= 0)
|
if ((clusters?.length ?? 0) <= 0)
|
||||||
return defaultViewParams
|
return { center: [60.81226, 70.0562], zoom: 5 }
|
||||||
|
|
||||||
const center = clusters.reduce((sum, cluster) => {
|
const center = clusters.reduce((sum, cluster) => {
|
||||||
sum[0] += (cluster.latitude / clusters.length)
|
sum[0] += cluster.latitude
|
||||||
sum[1] += (cluster.longitude / clusters.length)
|
sum[1] += cluster.longitude
|
||||||
return sum
|
return sum
|
||||||
}, [0, 0])
|
}, [0, 0]).map((elm) => elm / clusters.length)
|
||||||
|
|
||||||
const maxDeg = clusters.reduce((max, cluster) => {
|
const maxDeg = clusters.reduce((max, cluster) => {
|
||||||
const dLatitude = Math.abs(center[0] - cluster.latitude)
|
const dLatitude = Math.abs(center[0] - cluster.latitude)
|
||||||
const dLongitude = Math.abs(center[1] - cluster.longitude)
|
const dLongitude = Math.abs(center[1] - cluster.longitude)
|
||||||
const d = dLatitude > dLongitude ? dLatitude : dLongitude
|
return Math.max(Math.max(dLatitude, dLongitude), max)
|
||||||
return d > max ? d : max
|
}, 0)
|
||||||
}, 0)
|
|
||||||
|
|
||||||
// zoom max = 20 (too close)
|
// zoom max = 20 (too close)
|
||||||
// zoom min = 1 (mega far)
|
// zoom min = 1 (mega far)
|
||||||
// 4 - full Russia (161.6 deg)
|
// 4 - full Russia (161.6 deg)
|
||||||
// 13.5 - Khanty-Mansiysk
|
// 13.5 - Khanty-Mansiysk
|
||||||
const zoom = zoomLimit(5 + 5 / (maxDeg + 0.5))
|
const zoom = zoomLimit(5 + 5 / (maxDeg + 0.5))
|
||||||
|
|
||||||
return { center, zoom }
|
return { center, zoom }
|
||||||
}
|
}
|
||||||
|
|
||||||
const Deposit = memo(() => {
|
const Deposit = memo(() => {
|
||||||
const [depositsData, setDepositsData] = useState([])
|
const deposits = useDeposits()
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const setLayoutProps = useLayoutProps()
|
||||||
const [viewParams, setViewParams] = useState(defaultViewParams)
|
const location = useLocation()
|
||||||
|
|
||||||
const setLayoutProps = useLayoutProps()
|
const makeDepositLinks = useCallback((clusters) => (
|
||||||
|
<div>
|
||||||
const location = useLocation()
|
{clusters.map(cluster => (
|
||||||
|
<Link
|
||||||
const selectorProps = useMemo(() => {
|
key={cluster.id}
|
||||||
const hasId = location.pathname.length > '/deposit/'.length
|
to={{
|
||||||
|
pathname: `/cluster/${cluster.id}`,
|
||||||
return {
|
state: { from: location.pathname }
|
||||||
expand: hasId ? [location.pathname] : true,
|
}}
|
||||||
current: hasId ? location.pathname : undefined,
|
>
|
||||||
}
|
<div>{cluster.caption}</div>
|
||||||
}, [location.pathname])
|
</Link>
|
||||||
|
|
||||||
useEffect(() => setLayoutProps({
|
|
||||||
sheet: false,
|
|
||||||
showSelector: true,
|
|
||||||
selectorProps,
|
|
||||||
title: 'Месторождение',
|
|
||||||
}), [setLayoutProps, selectorProps])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const deposits = await DepositService.getDeposits()
|
|
||||||
setDepositsData(arrayOrDefault(deposits))
|
|
||||||
setViewParams(calcViewParams(deposits))
|
|
||||||
},
|
|
||||||
setShowLoader,
|
|
||||||
`Не удалось загрузить список кустов`,
|
|
||||||
{ actionName: 'Получить список кустов' }
|
|
||||||
)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FastRunMenu />
|
|
||||||
<LoaderPortal show={showLoader}>
|
|
||||||
<div className={'h-100vh'} style={{ overflow: 'hidden' }}>
|
|
||||||
<Map {...viewParams}>
|
|
||||||
{depositsData.map(deposit => (
|
|
||||||
<Overlay
|
|
||||||
width={32}
|
|
||||||
anchor={[deposit.latitude, deposit.longitude]}
|
|
||||||
key={`${deposit.latitude} ${deposit.longitude}`}
|
|
||||||
>
|
|
||||||
<Popover content={
|
|
||||||
<div>
|
|
||||||
{deposit.clusters.map(cluster => (
|
|
||||||
<Link key={cluster.id} to={{ pathname: `/cluster/${cluster.id}`, state: { from: location.pathname }}}>
|
|
||||||
<div>{cluster.caption}</div>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
} trigger={['click']} title={deposit.caption}>
|
|
||||||
<div style={{cursor: 'pointer'}}>
|
|
||||||
<Badge count={deposit.clusters.length}>
|
|
||||||
<PointerIcon state={'active'} width={48} height={59} />
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</Overlay>
|
|
||||||
))}
|
))}
|
||||||
</Map>
|
|
||||||
</div>
|
</div>
|
||||||
</LoaderPortal>
|
), [location.pathname])
|
||||||
</>
|
|
||||||
)
|
const viewParams = useMemo(() => calcViewParams(deposits), [deposits])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const hasId = location.pathname.length > '/deposit/'.length
|
||||||
|
|
||||||
|
const selectorProps = {
|
||||||
|
expand: hasId ? [location.pathname] : true,
|
||||||
|
current: hasId ? location.pathname : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayoutProps({
|
||||||
|
sheet: false,
|
||||||
|
showSelector: true,
|
||||||
|
selectorProps,
|
||||||
|
title: 'Месторождение',
|
||||||
|
})
|
||||||
|
}, [setLayoutProps, location.pathname])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FastRunMenu />
|
||||||
|
<div className={'deposit-page'}>
|
||||||
|
<Map {...viewParams}>
|
||||||
|
{deposits.map(deposit => {
|
||||||
|
const anchor = [deposit.latitude, deposit.longitude]
|
||||||
|
const links = makeDepositLinks(deposit.clusters)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Overlay width={32} anchor={anchor} key={anchor.join(' ')}>
|
||||||
|
<Popover content={links} trigger={['click']} title={deposit.caption}>
|
||||||
|
<div className={'pointer'}>
|
||||||
|
<Badge count={deposit.clusters.length}>
|
||||||
|
<PointerIcon state={'active'} width={48} height={59} />
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</Overlay>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Map>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default withPermissions(Deposit, ['Cluster.get'])
|
export default withPermissions(Deposit, ['Cluster.get'])
|
||||||
|
@ -15,7 +15,7 @@ import { formatDate, range, withPermissions } from '@utils'
|
|||||||
import { TelemetryDataSaubService } from '@api'
|
import { TelemetryDataSaubService } from '@api'
|
||||||
|
|
||||||
import { normalizeData } from '../TelemetryView'
|
import { normalizeData } from '../TelemetryView'
|
||||||
import cursorRender from '../TelemetryView/cursorRender'
|
import { cursorRender } from '../TelemetryView/cursorRender'
|
||||||
import { makeChartGroups, yAxis } from '../TelemetryView/dataset'
|
import { makeChartGroups, yAxis } from '../TelemetryView/dataset'
|
||||||
|
|
||||||
const DATA_COUNT = 2048 // Колличество точек на подгрузку графика
|
const DATA_COUNT = 2048 // Колличество точек на подгрузку графика
|
||||||
@ -223,7 +223,7 @@ const Archive = memo(() => {
|
|||||||
const chartData = useMemo(() => cutData(dataSaub, domain.min, domain.max), [dataSaub, domain])
|
const chartData = useMemo(() => cutData(dataSaub, domain.min, domain.max), [dataSaub, domain])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader} style={{ flexGrow: 1 }}>
|
||||||
<Flex style={{margin: '8px 8px 0'}}>
|
<Flex style={{margin: '8px 8px 0'}}>
|
||||||
<div>
|
<div>
|
||||||
Начальная дата:
|
Начальная дата:
|
||||||
@ -259,6 +259,7 @@ const Archive = memo(() => {
|
|||||||
render: cursorRender,
|
render: cursorRender,
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
style={{ flexGrow: 1 }}
|
||||||
height={'76vh'}
|
height={'76vh'}
|
||||||
onWheel={onGraphWheel}
|
onWheel={onGraphWheel}
|
||||||
/>
|
/>
|
||||||
|
@ -28,7 +28,7 @@ const categoryDictionary = {
|
|||||||
|
|
||||||
// Конфигурация таблицы
|
// Конфигурация таблицы
|
||||||
export const makeMessageColumns = (idWell) => [
|
export const makeMessageColumns = (idWell) => [
|
||||||
makeDateColumn('Дата', 'date', undefined, undefined, { width: '10rem' }),
|
makeDateColumn('Дата', 'date', undefined, undefined, { width: '120px' }),
|
||||||
makeNumericColumn('Глубина, м', 'wellDepth', null, null, (depth, item) => (
|
makeNumericColumn('Глубина, м', 'wellDepth', null, null, (depth, item) => (
|
||||||
<Tooltip title={'Нажмите для перехода в архив'}>
|
<Tooltip title={'Нажмите для перехода в архив'}>
|
||||||
<Link
|
<Link
|
||||||
@ -50,7 +50,7 @@ export const makeMessageColumns = (idWell) => [
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
}),
|
}),
|
||||||
makeColumn('Сообщение', 'message', { onFilter: (value, record) => record.name.indexOf(value) === 0 }),
|
makeColumn('Сообщение', 'message', { onFilter: (value, record) => record.name.indexOf(value) === 0 }),
|
||||||
makeTextColumn('Пользователь', 'user', null, null, null, { width: '10rem' }),
|
makeTextColumn('Пользователь', 'user', null, null, null, { width: '120px' }),
|
||||||
]
|
]
|
||||||
|
|
||||||
const filterOptions = [
|
const filterOptions = [
|
||||||
|
@ -24,15 +24,15 @@ import WirelineRunOut from './WirelineRunOut'
|
|||||||
import { CustomColumn } from './CustomColumn'
|
import { CustomColumn } from './CustomColumn'
|
||||||
import { ModeDisplay } from './ModeDisplay'
|
import { ModeDisplay } from './ModeDisplay'
|
||||||
import { UserOfWell } from './UserOfWells'
|
import { UserOfWell } from './UserOfWells'
|
||||||
import cursorRender from './cursorRender'
|
|
||||||
import { Setpoints } from './Setpoints'
|
import { Setpoints } from './Setpoints'
|
||||||
|
import { cursorRender } from './cursorRender'
|
||||||
|
|
||||||
import MomentStabPicEnabled from '@images/DempherOn.png'
|
import MomentStabPicEnabled from '@images/DempherOn.png'
|
||||||
import MomentStabPicDisabled from '@images/DempherOff.png'
|
import MomentStabPicDisabled from '@images/DempherOff.png'
|
||||||
import SpinPicEnabled from '@images/SpinEnabled.png'
|
import SpinPicEnabled from '@images/SpinEnabled.png'
|
||||||
import SpinPicDisabled from '@images/SpinDisabled.png'
|
import SpinPicDisabled from '@images/SpinDisabled.png'
|
||||||
|
|
||||||
import '@styles/monitoring.less'
|
import '@styles/telemetry_view.less'
|
||||||
import '@styles/message.less'
|
import '@styles/message.less'
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
@ -64,6 +64,7 @@ const makeSubjectSubsription = (subject$, handler) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TelemetryView = memo(() => {
|
const TelemetryView = memo(() => {
|
||||||
|
const [currentWellId, setCurrentWellId] = useState(null)
|
||||||
const [dataSaub, setDataSaub] = useState([])
|
const [dataSaub, setDataSaub] = useState([])
|
||||||
const [dataSpin, setDataSpin] = useState([])
|
const [dataSpin, setDataSpin] = useState([])
|
||||||
const [chartInterval, setChartInterval] = useState(defaultPeriod)
|
const [chartInterval, setChartInterval] = useState(defaultPeriod)
|
||||||
@ -78,12 +79,12 @@ const TelemetryView = memo(() => {
|
|||||||
const saubSubject$ = useMemo(() => new BehaviorSubject(), [])
|
const saubSubject$ = useMemo(() => new BehaviorSubject(), [])
|
||||||
const spinSubject$ = useMemo(() => new BehaviorSubject(), [])
|
const spinSubject$ = useMemo(() => new BehaviorSubject(), [])
|
||||||
|
|
||||||
const handleDataSaub = useCallback((data) => {
|
const handleDataSaub = useCallback((data, replace = false) => {
|
||||||
if (!data) return
|
|
||||||
|
|
||||||
const dataSaub = normalizeData(data)
|
|
||||||
setDataSaub((prev) => {
|
setDataSaub((prev) => {
|
||||||
const out = [...prev, ...dataSaub]
|
if (!data)
|
||||||
|
return replace ? [] : prev
|
||||||
|
const dataSaub = normalizeData(data)
|
||||||
|
const out = replace ? [...dataSaub] : [...prev, ...dataSaub]
|
||||||
out.sort(dateSorter)
|
out.sort(dateSorter)
|
||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
@ -115,6 +116,8 @@ const TelemetryView = memo(() => {
|
|||||||
useEffect(() => makeSubjectSubsription(spinSubject$, handleDataSpin), [spinSubject$, handleDataSpin])
|
useEffect(() => makeSubjectSubsription(spinSubject$, handleDataSpin), [spinSubject$, handleDataSpin])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (currentWellId == well.id) return
|
||||||
|
setCurrentWellId(well.id)
|
||||||
invokeWebApiWrapperAsync(
|
invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const flowChart = await DrillFlowChartService.getByIdWell(well.id)
|
const flowChart = await DrillFlowChartService.getByIdWell(well.id)
|
||||||
@ -128,7 +131,7 @@ const TelemetryView = memo(() => {
|
|||||||
`Не удалось получить данные`,
|
`Не удалось получить данные`,
|
||||||
{ actionName: 'Получение данных по скважине', well }
|
{ actionName: 'Получение данных по скважине', well }
|
||||||
)
|
)
|
||||||
}, [well, chartInterval, handleDataSpin, handleDataSaub])
|
}, [well, chartInterval, currentWellId, handleDataSaub])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = Subscribe(
|
const unsubscribe = Subscribe(
|
||||||
@ -163,7 +166,7 @@ const TelemetryView = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<Grid style={{ gridTemplateColumns: 'auto repeat(6, 1fr)' }}>
|
<Grid className={'telemetry-view-page'} style={{ gridTemplateColumns: 'auto repeat(6, 1fr)' }}>
|
||||||
<GridItem col={'1'} row={'1'} colSpan={'8'} style={{ marginBottom: '0.5rem' }}>
|
<GridItem col={'1'} row={'1'} colSpan={'8'} style={{ marginBottom: '0.5rem' }}>
|
||||||
<div className={'page-top'}>
|
<div className={'page-top'}>
|
||||||
<ModeDisplay data={dataSaub} />
|
<ModeDisplay data={dataSaub} />
|
||||||
|
@ -10,7 +10,7 @@ import { arrayOrDefault } from '@utils'
|
|||||||
const columns = [
|
const columns = [
|
||||||
makeNumericStartEnd('Глубина, м', 'depth'),
|
makeNumericStartEnd('Глубина, м', 'depth'),
|
||||||
makeNumericMinMax('Нагрузка, т', 'axialLoad'),
|
makeNumericMinMax('Нагрузка, т', 'axialLoad'),
|
||||||
makeNumericMinMax('Давление, атм', 'pressure'),
|
makeNumericMinMax('Перепад давления, атм', 'pressure'),
|
||||||
makeNumericMinMax('Момент на ВСП, кН·м', 'rotorTorque'),
|
makeNumericMinMax('Момент на ВСП, кН·м', 'rotorTorque'),
|
||||||
makeNumericMinMax('Обороты на ВСП, об/мин', 'rotorSpeed'),
|
makeNumericMinMax('Обороты на ВСП, об/мин', 'rotorSpeed'),
|
||||||
makeNumericMinMax('Расход, л/с', 'flow')
|
makeNumericMinMax('Расход, л/с', 'flow')
|
||||||
|
@ -26,7 +26,7 @@ export const getColumns = async (idWell) => {
|
|||||||
sorter: makeNumericSorter('idWellSectionType'),
|
sorter: makeNumericSorter('idWellSectionType'),
|
||||||
}),
|
}),
|
||||||
makeNumericAvgRange('Нагрузка, т', 'axialLoad', 1),
|
makeNumericAvgRange('Нагрузка, т', 'axialLoad', 1),
|
||||||
makeNumericAvgRange('Давление, атм', 'pressure', 1),
|
makeNumericAvgRange('Перепад давления, атм', 'pressure', 1),
|
||||||
makeNumericAvgRange('Момент на ВСП, кН·м', 'rotorTorque', 1),
|
makeNumericAvgRange('Момент на ВСП, кН·м', 'rotorTorque', 1),
|
||||||
makeNumericAvgRange('Обороты на ВСП, об/мин', 'rotorSpeed', 1),
|
makeNumericAvgRange('Обороты на ВСП, об/мин', 'rotorSpeed', 1),
|
||||||
makeNumericAvgRange('Расход, л/с', 'flow', 1),
|
makeNumericAvgRange('Расход, л/с', 'flow', 1),
|
||||||
|
@ -7,8 +7,5 @@
|
|||||||
// Переменные для темы тут:
|
// Переменные для темы тут:
|
||||||
// https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
|
// https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
|
||||||
|
|
||||||
//@primary-color: rgba(124, 124, 124, 0.3);
|
@primary-color: #C32828; // rgb(195, 40, 40)
|
||||||
@primary-color: rgb(195, 40,40);
|
@layout-header-background:#413F3D; // rgb(65, 63, 61)
|
||||||
//@primary-color:rgb(65, 63, 61);
|
|
||||||
//@layout-header-background: rgb(195, 40,40);
|
|
||||||
@layout-header-background: rgb(65, 63, 61);
|
|
||||||
|
@ -135,7 +135,7 @@ code {
|
|||||||
-moz-user-select: none; /* Old versions of Firefox */
|
-moz-user-select: none; /* Old versions of Firefox */
|
||||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
user-select: none; /* Non-prefixed version, currently
|
user-select: none; /* Non-prefixed version, currently
|
||||||
supported by Chrome, Edge, Opera and Firefox */
|
supported by Chrome, Edge, Opera and Firefox */
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-link {
|
.download-link {
|
||||||
@ -153,4 +153,17 @@ code {
|
|||||||
|
|
||||||
.color-pale-green {
|
.color-pale-green {
|
||||||
background-color: #98fb98;
|
background-color: #98fb98;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.asb-grid-item {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deposit-page {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-layout {
|
.page-layout {
|
||||||
|
--sheet-padding: 15px;
|
||||||
|
|
||||||
|
@sheet-padding: var(--sheet-padding);
|
||||||
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
||||||
& .menu-sider {
|
& .menu-sider {
|
||||||
@ -62,7 +66,7 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@ -88,8 +92,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 15px;
|
gap: @sheet-padding;
|
||||||
padding: 0 15px 15px 15px;
|
padding: @sheet-padding;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
& .ant-breadcrumb-link, .ant-breadcrumb-separator {
|
& .ant-breadcrumb-link, .ant-breadcrumb-separator {
|
||||||
.no-select;
|
.no-select;
|
||||||
@ -99,7 +104,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& .sheet{
|
& .sheet{
|
||||||
padding: 15px;
|
padding: @sheet-padding;
|
||||||
// min-height: calc(@layout-min-height - 30px); // 280px;
|
// min-height: calc(@layout-min-height - 30px); // 280px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
// margin: 15px;
|
// margin: 15px;
|
||||||
@ -126,3 +131,15 @@
|
|||||||
.site-layout-background {
|
.site-layout-background {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1280px) {
|
||||||
|
.page-layout {
|
||||||
|
--sheet-padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1024px) {
|
||||||
|
.page-layout {
|
||||||
|
--sheet-padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -39,11 +39,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loader-content{
|
.loader-content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
grid-column-end: span 3;
|
grid-column-end: span 3;
|
||||||
grid-row-start: 1;
|
grid-row-start: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loader-content.loader-content-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.loader-overlay{
|
.loader-overlay{
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
grid-column-end: span 3;
|
grid-column-end: span 3;
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
.page-top {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: -5px;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .icons {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
66
src/styles/telemetry_view.less
Normal file
66
src/styles/telemetry_view.less
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
.telemetry-view-page {
|
||||||
|
--page-gap: 10px;
|
||||||
|
|
||||||
|
@page-gap: var(--page-gap);
|
||||||
|
|
||||||
|
.flex-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: @page-gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
.flex-container;
|
||||||
|
|
||||||
|
& .page-top {
|
||||||
|
.flex-container;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
& .icons {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .page-main {
|
||||||
|
flex-grow: 1;
|
||||||
|
.flex-container;
|
||||||
|
|
||||||
|
& .page-left {
|
||||||
|
.flex-container;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
& .modes, & .current-values {
|
||||||
|
.flex-container;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .page-right {
|
||||||
|
flex-grow: 1;
|
||||||
|
.flex-container;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1280px) {
|
||||||
|
.telemetry-view-page {
|
||||||
|
--page-gap: 7.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1024px) {
|
||||||
|
.telemetry-view-page {
|
||||||
|
--page-gap: 5px;
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: red;
|
background-color: #FFF2F0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .avg-value {
|
& .avg-value {
|
||||||
|
Loading…
Reference in New Issue
Block a user