forked from ddrilling/asb_cloud_front
* Мониторинг переписан на flex
* Блок текущих значений перемещён наверх и переписан * Выделена строка управления графиком * Удалена мнемосхема
This commit is contained in:
parent
b2c34d07a9
commit
11a632c246
@ -1,101 +0,0 @@
|
|||||||
import moment from 'moment'
|
|
||||||
import { useState, useEffect, memo, ReactNode } from 'react'
|
|
||||||
import {CaretUpOutlined, CaretDownOutlined, CaretRightOutlined} from '@ant-design/icons'
|
|
||||||
|
|
||||||
import '@styles/display.less'
|
|
||||||
|
|
||||||
export const formatNumber = (value?: unknown, format?: number) =>
|
|
||||||
Number.isInteger(format) && Number.isFinite(value)
|
|
||||||
? Number(value).toFixed(format)
|
|
||||||
: Number(value).toPrecision(4)
|
|
||||||
|
|
||||||
const iconStyle = { color:'#0008' }
|
|
||||||
const displayValueStyle = { display: 'flex', flexGrow: 1 }
|
|
||||||
|
|
||||||
export type ValueDisplayProps = {
|
|
||||||
prefix?: ReactNode
|
|
||||||
suffix?: ReactNode
|
|
||||||
format?: number | string | ((arg: string) => ReactNode)
|
|
||||||
isArrowVisible?: boolean
|
|
||||||
enumeration?: Record<string, string>
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DisplayProps = ValueDisplayProps & {
|
|
||||||
className?: string
|
|
||||||
label?: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ValueDisplay = memo<ValueDisplayProps>(({ prefix, value, suffix, isArrowVisible, format, enumeration }) => {
|
|
||||||
const [val, setVal] = useState<ReactNode>('---')
|
|
||||||
const [arrowState, setArrowState] = useState({
|
|
||||||
preVal: NaN,
|
|
||||||
preTimestamp: Date.now(),
|
|
||||||
direction: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setVal((preVal) => {
|
|
||||||
if ((value ?? '-') === '-' || value === '--') return '---'
|
|
||||||
if (typeof format === 'function') return format(enumeration?.[value] ?? value)
|
|
||||||
if (enumeration?.[value]) return enumeration[value]
|
|
||||||
|
|
||||||
if (Number.isFinite(+value)) {
|
|
||||||
if (isArrowVisible && (arrowState.preTimestamp + 1000 < Date.now())) {
|
|
||||||
let direction = 0
|
|
||||||
if (+value > arrowState.preVal)
|
|
||||||
direction = 1
|
|
||||||
if (+value < arrowState.preVal)
|
|
||||||
direction = -1
|
|
||||||
|
|
||||||
setArrowState({
|
|
||||||
preVal: +value,
|
|
||||||
preTimestamp: Date.now(),
|
|
||||||
direction: direction,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatNumber(value, Number(format))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.length > 4) {
|
|
||||||
const valueDate = moment(value)
|
|
||||||
if (valueDate.isValid())
|
|
||||||
return valueDate.format(String(format))
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
})
|
|
||||||
},[value, isArrowVisible, arrowState, format, enumeration])
|
|
||||||
|
|
||||||
let arrow = null
|
|
||||||
if(isArrowVisible)
|
|
||||||
switch (arrowState.direction){
|
|
||||||
case 0:
|
|
||||||
arrow = <CaretRightOutlined style={iconStyle}/>
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
arrow = <CaretUpOutlined style={iconStyle}/>
|
|
||||||
break
|
|
||||||
case -1:
|
|
||||||
arrow = <CaretDownOutlined style={iconStyle}/>
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<span className={'display_value'}>
|
|
||||||
{prefix} {val} {suffix}{arrow}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Display = memo<DisplayProps>(({ className, label, ...other })=> (
|
|
||||||
<div className={className}>
|
|
||||||
<div className={'display_label'}>{label}</div>
|
|
||||||
<div style={displayValueStyle}>
|
|
||||||
<ValueDisplay {...other}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
@ -73,8 +73,8 @@ export type ChartGroup<DataType extends BaseDataType> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultOffsets: ChartOffset = {
|
const defaultOffsets: ChartOffset = {
|
||||||
top: 10,
|
top: 0,
|
||||||
bottom: 10,
|
bottom: 0,
|
||||||
left: 100,
|
left: 100,
|
||||||
right: 20,
|
right: 20,
|
||||||
}
|
}
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import moment from 'moment'
|
|
||||||
import { memo } from 'react'
|
|
||||||
import { Tooltip, Typography } from 'antd'
|
|
||||||
|
|
||||||
import { Display } from '@components/Display'
|
|
||||||
|
|
||||||
import RigMnemo from './RigMnemo'
|
|
||||||
|
|
||||||
const getTimeFormat = (value) => {
|
|
||||||
const date = moment(value)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip title={`Время последних данных: ${date.format('DD.MM.YYYY HH:mm:ss')}`}>
|
|
||||||
{date.isSame(new Date(), 'day') || (
|
|
||||||
<Typography.Text disabled style={{ fontSize: '12px', marginRight: '5px' }}>{date.format('DD.MM.YYYY')}</Typography.Text>
|
|
||||||
)}
|
|
||||||
{date.format('HH:mm:ss')}
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = [
|
|
||||||
{ label: 'Рот., об/мин', accessorName: 'rotorSpeed', isArrowVisible: true },
|
|
||||||
{ label: 'Долото, м', accessorName: 'bitDepth', isArrowVisible: true, format: 2 },
|
|
||||||
{ label: 'Забой, м', accessorName: 'wellDepth', isArrowVisible: true, format: 2 },
|
|
||||||
{ label: 'Расход, м³/ч', accessorName: 'flow', isArrowVisible: true },
|
|
||||||
{ label: 'Расход х.х., м³/ч', accessorName: 'flowIdle', isArrowVisible: true },
|
|
||||||
{ label: 'Время', accessorName: 'date', format: getTimeFormat },
|
|
||||||
{ label: 'MSE, %', accessorName: 'mse', format: 2 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const CustomColumn = memo(({ data }) => {
|
|
||||||
const dataLast = data[data.length - 1]
|
|
||||||
params.forEach(param => param.value = dataLast?.[param.accessorName] ?? '-')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{params.map(param => (
|
|
||||||
<Display
|
|
||||||
className={'border_small display_flex_container'}
|
|
||||||
key={param.label}
|
|
||||||
label={param.label}
|
|
||||||
value={param.value}
|
|
||||||
suffix={param.units}
|
|
||||||
format={param.format}
|
|
||||||
isArrowVisible={param.isArrowVisible}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<RigMnemo
|
|
||||||
wellDepth={dataLast?.wellDepth ?? Number.NaN}
|
|
||||||
bitPosition={dataLast?.bitDepth ?? Number.NaN}
|
|
||||||
blockPosition={dataLast?.blockPosition ?? Number.NaN}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default CustomColumn
|
|
@ -1,24 +0,0 @@
|
|||||||
import { memo } from 'react'
|
|
||||||
|
|
||||||
import { Display } from '@components/Display'
|
|
||||||
|
|
||||||
const modeNames = {
|
|
||||||
0: 'Ручной',
|
|
||||||
1: 'Бурение в роторе',
|
|
||||||
2: 'Проработка',
|
|
||||||
3: 'Бурение в слайде',
|
|
||||||
4: 'Спуск СПО',
|
|
||||||
5: 'Подъем СПО',
|
|
||||||
6: 'Подъем с проработкой',
|
|
||||||
|
|
||||||
10: 'БЛОКИРОВКА',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ModeDisplay = memo(({ data }) => (
|
|
||||||
<Display
|
|
||||||
className={'border_small display_flex_container user_card'}
|
|
||||||
label={'Режим:'}
|
|
||||||
value={data?.[data?.length - 1]?.mode}
|
|
||||||
enumeration={modeNames}
|
|
||||||
/>
|
|
||||||
))
|
|
107
src/pages/Well/Telemetry/TelemetryView/TelemetrySummary.jsx
Normal file
107
src/pages/Well/Telemetry/TelemetryView/TelemetrySummary.jsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { isValidElement, memo, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { CaretUpOutlined, CaretDownOutlined, CaretRightOutlined } from '@ant-design/icons'
|
||||||
|
import { Tooltip, Typography } from 'antd'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import { formatDate, isRawDate } from '@utils'
|
||||||
|
|
||||||
|
import '@styles/components/data_summary.less'
|
||||||
|
|
||||||
|
export const parseValue = (value, formatter) => {
|
||||||
|
if (!value || String(value).trim().length <= 0) return '---'
|
||||||
|
if (typeof formatter === 'function') return formatter(value)
|
||||||
|
if (isRawDate(value)) return formatDate(value)
|
||||||
|
const v = +value
|
||||||
|
if (Number.isFinite(v))
|
||||||
|
return Number.isInteger(formatter) ? v.toFixed(formatter) : v.toPrecision(4)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DashboardDisplay = memo(({ label, title, unit, iconRenderer, value, format }) => {
|
||||||
|
const [icon, setIcon] = useState(null)
|
||||||
|
|
||||||
|
const val = useMemo(() => parseValue(value, format), [value, format])
|
||||||
|
|
||||||
|
useEffect(() => setIcon((prev) => iconRenderer?.(value, prev)), [value])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'dashboard-display'}>
|
||||||
|
<div className={'display-label'}>
|
||||||
|
{title ? (
|
||||||
|
<Tooltip title={title}>{label}</Tooltip>
|
||||||
|
) : (
|
||||||
|
<span>{label}</span>
|
||||||
|
)}
|
||||||
|
<span>{unit}</span>
|
||||||
|
</div>
|
||||||
|
<div className={'display-value'}>
|
||||||
|
<span>{val}</span>
|
||||||
|
{isValidElement(icon) ? icon : icon?.value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getTimeFormat = (value) => {
|
||||||
|
const date = moment(value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={`Время последних данных: ${date.format('DD.MM.YYYY HH:mm:ss')}`}>
|
||||||
|
{date.isSame(new Date(), 'day') || (
|
||||||
|
<Typography.Text disabled style={{ fontSize: '12px', marginRight: '5px' }}>{date.format('DD.MM.YYYY')}</Typography.Text>
|
||||||
|
)}
|
||||||
|
{date.format('HH:mm:ss')}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconRenderer = (value, prev) => {
|
||||||
|
if (!Number.isFinite(+value)) return null
|
||||||
|
if (prev?.prevDate + 1000 >= Date.now()) return prev
|
||||||
|
const val = +value
|
||||||
|
let Component = CaretRightOutlined
|
||||||
|
if ((prev?.prev ?? null) && val !== prev.prev)
|
||||||
|
Component = val > prev.prev ? CaretUpOutlined : CaretDownOutlined
|
||||||
|
|
||||||
|
return {
|
||||||
|
prev: val,
|
||||||
|
prevDate: Date.now(),
|
||||||
|
value: <Component style={{ color:'#0008' }} />,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const modeNames = {
|
||||||
|
0: 'Ручной',
|
||||||
|
1: 'Бурение в роторе',
|
||||||
|
2: 'Проработка',
|
||||||
|
3: 'Бурение в слайде',
|
||||||
|
4: 'Спуск СПО',
|
||||||
|
5: 'Подъем СПО',
|
||||||
|
6: 'Подъем с проработкой',
|
||||||
|
|
||||||
|
10: 'БЛОКИРОВКА',
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = [
|
||||||
|
{ label: 'Режим', accessorName: 'mode', format: (value) => modeNames[value] || '---' },
|
||||||
|
{ label: 'Пользователь', accessorName: 'user', title: 'Пользователь панели оператора' },
|
||||||
|
{ label: 'Рот.', unit: 'об/мин', accessorName: 'rotorSpeed', iconRenderer },
|
||||||
|
{ label: 'Долото', unit: 'м', accessorName: 'bitDepth', iconRenderer, format: 2 },
|
||||||
|
{ label: 'Забой', unit: 'м', accessorName: 'wellDepth', iconRenderer, format: 2 },
|
||||||
|
{ label: 'Расход', unit: 'м³/ч', accessorName: 'flow', iconRenderer },
|
||||||
|
{ label: 'Расход х.х.', unit: 'м³/ч', accessorName: 'flowIdle', iconRenderer },
|
||||||
|
{ label: 'Время', accessorName: 'date', format: getTimeFormat },
|
||||||
|
{ label: 'MSE', unit: '%', accessorName: 'mse', format: 2 },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const TelemetrySummary = memo(({ data }) => {
|
||||||
|
return (
|
||||||
|
<div className={'data-summary'}>
|
||||||
|
{params.map((param, i) => (
|
||||||
|
<DashboardDisplay key={i} {...param} value={data?.[param.accessorName]} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TelemetrySummary
|
@ -1,12 +0,0 @@
|
|||||||
import { Tooltip } from 'antd'
|
|
||||||
import { Display } from '@components/Display'
|
|
||||||
|
|
||||||
export const UserOfWell = ({ data }) => (
|
|
||||||
<Display
|
|
||||||
className={'border_small display_flex_container user_card'}
|
|
||||||
label={<Tooltip title={'Пользователь панели оператора'}>Пользователь</Tooltip>}
|
|
||||||
value={data[data.length - 1]?.user}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default UserOfWell
|
|
@ -1,15 +1,15 @@
|
|||||||
import { useState, useEffect, useCallback, memo, useMemo } from 'react'
|
import { useState, useEffect, useCallback, memo, useMemo } from 'react'
|
||||||
import { BehaviorSubject, buffer, throttleTime } from 'rxjs'
|
import { BehaviorSubject, buffer, throttleTime } from 'rxjs'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { Button, Select } from 'antd'
|
import { Button, Select } from 'antd'
|
||||||
|
|
||||||
import { useWell } from '@asb/context'
|
import { useWell } from '@asb/context'
|
||||||
import { makeDateSorter } from '@components/Table'
|
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
||||||
import { D3MonitoringCharts } from '@components/d3/monitoring'
|
import { D3MonitoringCharts } from '@components/d3/monitoring'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
import { formatDate, hasPermission, withPermissions } from '@utils'
|
import { formatDate, hasPermission, isRawDate, range, withPermissions } from '@utils'
|
||||||
import { Subscribe } from '@services/signalr'
|
import { Subscribe } from '@services/signalr'
|
||||||
import {
|
import {
|
||||||
DrillFlowChartService,
|
DrillFlowChartService,
|
||||||
@ -20,10 +20,8 @@ import {
|
|||||||
|
|
||||||
import { makeChartGroups, yAxis } from './dataset'
|
import { makeChartGroups, yAxis } from './dataset'
|
||||||
import ActiveMessagesOnline from './ActiveMessagesOnline'
|
import ActiveMessagesOnline from './ActiveMessagesOnline'
|
||||||
|
import TelemetrySummary from './TelemetrySummary'
|
||||||
import WirelineRunOut from './WirelineRunOut'
|
import WirelineRunOut from './WirelineRunOut'
|
||||||
import { CustomColumn } from './CustomColumn'
|
|
||||||
import { ModeDisplay } from './ModeDisplay'
|
|
||||||
import { UserOfWell } from './UserOfWells'
|
|
||||||
import { Setpoints } from './Setpoints'
|
import { Setpoints } from './Setpoints'
|
||||||
import { cursorRender } from './cursorRender'
|
import { cursorRender } from './cursorRender'
|
||||||
|
|
||||||
@ -37,6 +35,14 @@ import '@styles/message.less'
|
|||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
|
const chartProps = {
|
||||||
|
yAxis,
|
||||||
|
chartName: 'monitoring',
|
||||||
|
yTicks: { visible: true, format: (d) => formatDate(d, 'YYYY-MM-DD') },
|
||||||
|
plugins: { menu: { enabled: false }, cursor: { render: cursorRender } },
|
||||||
|
style: { flexGrow: 1, height: 'auto', width: 'auto' },
|
||||||
|
}
|
||||||
|
|
||||||
const getLast = (data) => Array.isArray(data) ? data.at(-1) : data
|
const getLast = (data) => Array.isArray(data) ? data.at(-1) : data
|
||||||
|
|
||||||
const isMseEnabled = (dataSaub) => (getLast(dataSaub)?.mseState && 2) > 0
|
const isMseEnabled = (dataSaub) => (getLast(dataSaub)?.mseState && 2) > 0
|
||||||
@ -54,6 +60,7 @@ export const normalizeData = (data) => data?.map(item => ({
|
|||||||
})) ?? []
|
})) ?? []
|
||||||
|
|
||||||
const dateSorter = makeDateSorter('date')
|
const dateSorter = makeDateSorter('date')
|
||||||
|
const defaultDate = () => Date.now() - defaultPeriod * 1000
|
||||||
|
|
||||||
const makeSubjectSubsription = (subject$, handler) => {
|
const makeSubjectSubsription = (subject$, handler) => {
|
||||||
const subscribtion = subject$.pipe(
|
const subscribtion = subject$.pipe(
|
||||||
@ -64,20 +71,49 @@ const makeSubjectSubsription = (subject$, handler) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TelemetryView = memo(() => {
|
const TelemetryView = memo(() => {
|
||||||
|
const [well, updateWell] = useWell()
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
|
||||||
const [currentWellId, setCurrentWellId] = useState(null)
|
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 [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [flowChartData, setFlowChartData] = useState([])
|
const [flowChartData, setFlowChartData] = useState([])
|
||||||
const [rop, setRop] = useState(null)
|
const [rop, setRop] = useState(null)
|
||||||
const [domain, setDomain] = useState({})
|
const [domain, setDomain] = useState({})
|
||||||
const [chartMethods, setChartMethods] = useState()
|
const [chartMethods, setChartMethods] = useState()
|
||||||
|
|
||||||
const [well, updateWell] = useWell()
|
const [chartInterval, setChartInterval] = useState(defaultPeriod)
|
||||||
|
const [startDate, setStartDate] = useState(defaultDate)
|
||||||
|
const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() })
|
||||||
|
|
||||||
const saubSubject$ = useMemo(() => new BehaviorSubject(), [])
|
const [archiveMode, setArchiveMode] = useState(false)
|
||||||
const spinSubject$ = useMemo(() => new BehaviorSubject(), [])
|
|
||||||
|
const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well])
|
||||||
|
|
||||||
|
const isDateDisabled = useCallback((date) => {
|
||||||
|
if (!date) return false
|
||||||
|
const dt = new Date(date).setHours(0, 0, 0, 0)
|
||||||
|
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||||
|
}, [dateLimit, chartInterval])
|
||||||
|
|
||||||
|
const isDateTimeDisabled = useCallback((date) => ({
|
||||||
|
disabledHours: () => range(24).filter(h => {
|
||||||
|
if (!date) return false
|
||||||
|
const dt = +new Date(date).setHours(h)
|
||||||
|
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||||
|
}),
|
||||||
|
disabledMinutes: () => range(60).filter(m => {
|
||||||
|
if (!date) return false
|
||||||
|
const dt = +new Date(date).setMinutes(m)
|
||||||
|
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||||
|
}),
|
||||||
|
disabledSeconds: () => range(60).filter(s => {
|
||||||
|
if (!date) return false
|
||||||
|
const dt = +new Date(date).setSeconds(s)
|
||||||
|
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||||
|
})
|
||||||
|
}), [dateLimit, chartInterval])
|
||||||
|
|
||||||
const handleDataSaub = useCallback((data, replace = false) => {
|
const handleDataSaub = useCallback((data, replace = false) => {
|
||||||
setDataSaub((prev) => {
|
setDataSaub((prev) => {
|
||||||
@ -92,7 +128,21 @@ const TelemetryView = memo(() => {
|
|||||||
|
|
||||||
const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), [])
|
const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), [])
|
||||||
|
|
||||||
const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well])
|
const onWheel = useCallback((value) => {
|
||||||
|
if (!archiveMode && value.deltaY < 0) {
|
||||||
|
setArchiveMode(true)
|
||||||
|
// load data
|
||||||
|
} else {
|
||||||
|
// move
|
||||||
|
}
|
||||||
|
}, [archiveMode])
|
||||||
|
|
||||||
|
const spinLast = useMemo(() => dataSpin.at(-1), [dataSpin])
|
||||||
|
const saubLast = useMemo(() => dataSaub.at(-1), [dataSaub])
|
||||||
|
const summaryData = useMemo(() => ({ ...saubLast, ...rop }), [saubLast, rop])
|
||||||
|
|
||||||
|
const saubSubject$ = useMemo(() => new BehaviorSubject(), [])
|
||||||
|
const spinSubject$ = useMemo(() => new BehaviorSubject(), [])
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
let i, j
|
let i, j
|
||||||
@ -112,6 +162,32 @@ const TelemetryView = memo(() => {
|
|||||||
|
|
||||||
const chartGroups = useMemo(() => makeChartGroups(flowChartData), [flowChartData])
|
const chartGroups = useMemo(() => makeChartGroups(flowChartData), [flowChartData])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setArchiveMode(isRawDate(searchParams.get('start')))
|
||||||
|
const interval = parseInt(searchParams.get('range') || defaultPeriod)
|
||||||
|
const date = new Date(searchParams.get('start') || (Date.now() - interval))
|
||||||
|
setChartInterval(interval)
|
||||||
|
setStartDate(date)
|
||||||
|
}, [searchParams])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (archiveMode) return
|
||||||
|
const subscribtion = saubSubject$.pipe(
|
||||||
|
buffer(saubSubject$.pipe(throttleTime(700)))
|
||||||
|
).subscribe((data) => handleDataSaub(data.flat().filter(Boolean)))
|
||||||
|
|
||||||
|
return () => subscribtion.unsubscribe()
|
||||||
|
}, [saubSubject$, archiveMode])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (archiveMode) return
|
||||||
|
const subscribtion = spinSubject$.pipe(
|
||||||
|
buffer(spinSubject$.pipe(throttleTime(700)))
|
||||||
|
).subscribe((data) => handleDataSpin(data.flat().filter(Boolean)))
|
||||||
|
|
||||||
|
return () => subscribtion.unsubscribe()
|
||||||
|
}, [spinSubject$, archiveMode])
|
||||||
|
|
||||||
useEffect(() => makeSubjectSubsription(saubSubject$, handleDataSaub), [saubSubject$, handleDataSaub])
|
useEffect(() => makeSubjectSubsription(saubSubject$, handleDataSaub), [saubSubject$, handleDataSaub])
|
||||||
useEffect(() => makeSubjectSubsription(spinSubject$, handleDataSpin), [spinSubject$, handleDataSpin])
|
useEffect(() => makeSubjectSubsription(spinSubject$, handleDataSpin), [spinSubject$, handleDataSpin])
|
||||||
|
|
||||||
@ -148,6 +224,12 @@ const TelemetryView = memo(() => {
|
|||||||
async () => {
|
async () => {
|
||||||
const rop = await OperationStatService.getClusterRopStatByIdWell(well.id)
|
const rop = await OperationStatService.getClusterRopStatByIdWell(well.id)
|
||||||
setRop(rop)
|
setRop(rop)
|
||||||
|
let dates = await TelemetryDataSaubService.getDataDatesRange(well.id)
|
||||||
|
dates = {
|
||||||
|
from: new Date(dates?.from ?? 0),
|
||||||
|
to: new Date(dates?.to ?? 0)
|
||||||
|
}
|
||||||
|
setDateLimit(dates)
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить данные`,
|
`Не удалось загрузить данные`,
|
||||||
@ -156,74 +238,66 @@ const TelemetryView = memo(() => {
|
|||||||
}, [well])
|
}, [well])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dataSaub.length <= 0) return
|
if (!saubLast || archiveMode) return
|
||||||
const last = new Date(dataSaub.at(-1).date)
|
const last = new Date(saubLast.date)
|
||||||
setDomain({
|
const startDate = new Date(+last - chartInterval * 1000)
|
||||||
min: new Date(+last - chartInterval * 1000),
|
setStartDate(startDate)
|
||||||
max: last
|
setDomain({ min: startDate, max: last })
|
||||||
})
|
}, [archiveMode, saubLast, chartInterval])
|
||||||
}, [dataSaub, chartInterval])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader} style={{ flexGrow: 1 }}>
|
||||||
<Grid className={'telemetry-view-page'} style={{ gridTemplateColumns: 'auto repeat(6, 1fr)' }}>
|
<div className={'telemetry-view-page'}>
|
||||||
<GridItem col={'1'} row={'1'} colSpan={'8'} style={{ marginBottom: '0.5rem' }}>
|
<TelemetrySummary data={summaryData} />
|
||||||
<div className={'page-top'}>
|
<div className={'page-top'}>
|
||||||
<ModeDisplay data={dataSaub} />
|
<div>
|
||||||
<div>
|
Статус:
|
||||||
Интервал:
|
<Select value={well.idState ?? 0} onChange={onStatusChanged} disabled={!hasPermission('Well.edit')}>
|
||||||
<PeriodPicker onChange={setChartInterval} />
|
<Option value={0} disabled hidden>Неизвестно</Option>
|
||||||
</div>
|
<Option value={1}>В работе</Option>
|
||||||
<Button onClick={() => chartMethods?.setSettingsVisible(true)}>Настроить графики</Button>
|
<Option value={2}>Завершено</Option>
|
||||||
<div>
|
</Select>
|
||||||
Статус:
|
</div>
|
||||||
<Select value={well.idState ?? 0} onChange={onStatusChanged} disabled={!hasPermission('Well.edit')}>
|
<Setpoints />
|
||||||
<Option value={0} disabled hidden>Неизвестно</Option>
|
<WirelineRunOut />
|
||||||
<Option value={1}>В работе</Option>
|
<div className={'icons'}>
|
||||||
<Option value={2}>Завершено</Option>
|
<img src={isTorqueStabEnabled(spinLast) ? MomentStabPicEnabled : MomentStabPicDisabled} alt={'TorqueMaster'} />
|
||||||
</Select>
|
<img src={isSpinEnabled(spinLast) ? SpinPicEnabled : SpinPicDisabled} alt={'SpinMaster'} />
|
||||||
</div>
|
<h2 style={{ marginBottom: 0, fontWeight: 'bold', color: isMseEnabled(saubLast) ? 'green' : 'lightgrey' }}>MSE</h2>
|
||||||
<Setpoints />
|
</div>
|
||||||
<span style={{ flexGrow: 20 }}> </span>
|
</div>
|
||||||
<WirelineRunOut />
|
<div className={'page-top'}>
|
||||||
<div className={'icons'}>
|
<div>
|
||||||
<img src={isTorqueStabEnabled(dataSpin) ? MomentStabPicEnabled : MomentStabPicDisabled} alt={'TorqueMaster'} />
|
Начальная дата:
|
||||||
<img src={isSpinEnabled(dataSpin) ? SpinPicEnabled : SpinPicDisabled} alt={'SpinMaster'} />
|
<DatePickerWrapper
|
||||||
<h2 style={{ marginBottom: 0, fontWeight: 'bold', color: isMseEnabled(dataSaub) ? 'green' : 'lightgrey' }}>MSE</h2>
|
disabled={!archiveMode}
|
||||||
</div>
|
value={startDate}
|
||||||
<UserOfWell data={dataSaub} />
|
disabledDate={isDateDisabled}
|
||||||
</div>
|
disabledTime={isDateTimeDisabled}
|
||||||
</GridItem>
|
onChange={(startDate) => setStartDate(new Date(startDate))}
|
||||||
<GridItem col={'1'} row={'2'} rowSpan={'3'} style={{ minWidth: '260px', width: '0.142fr' }}>
|
/>
|
||||||
<CustomColumn data={dataSaub} />
|
</div>
|
||||||
</GridItem>
|
<div>
|
||||||
<GridItem col={2} row={2} colSpan={8} rowSpan={2}>
|
Интервал:
|
||||||
<D3MonitoringCharts
|
<PeriodPicker onChange={setChartInterval} />
|
||||||
methods={setChartMethods}
|
</div>
|
||||||
chartName={'monitoring'}
|
<Button onClick={() => chartMethods?.setSettingsVisible(true)}>Настроить графики</Button>
|
||||||
datasetGroups={chartGroups}
|
<Button onClick={() => setArchiveMode((prev) => !prev)}>
|
||||||
data={filteredData}
|
{archiveMode ? 'Выйти из архива' : 'Войти в архив'}
|
||||||
yDomain={domain}
|
</Button>
|
||||||
yAxis={yAxis}
|
</div>
|
||||||
yTicks={{
|
<D3MonitoringCharts
|
||||||
visible: true,
|
{...chartProps}
|
||||||
format: (d) => formatDate(d, 'YYYY-MM-DD')
|
yDomain={domain}
|
||||||
}}
|
data={filteredData}
|
||||||
plugins={{
|
methods={setChartMethods}
|
||||||
menu: { enabled: false },
|
datasetGroups={chartGroups}
|
||||||
cursor: {
|
onWheel={onWheel}
|
||||||
render: cursorRender,
|
/>
|
||||||
},
|
<ActiveMessagesOnline well={well} />
|
||||||
}}
|
</div>
|
||||||
height={'70vh'}
|
</LoaderPortal>
|
||||||
/>
|
)
|
||||||
</GridItem>
|
|
||||||
<GridItem col={'2'} row={'3'} colSpan={'7'}>
|
|
||||||
<ActiveMessagesOnline well={well} />
|
|
||||||
</GridItem>
|
|
||||||
</Grid>
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default withPermissions(TelemetryView, [
|
export default withPermissions(TelemetryView, [
|
||||||
|
56
src/styles/components/data_summary.less
Normal file
56
src/styles/components/data_summary.less
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
.data-summary {
|
||||||
|
display: flex;
|
||||||
|
gap: .5em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-display {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
border: .067em solid #D9D9D9;
|
||||||
|
gap: .2em;
|
||||||
|
border-radius: .14em;
|
||||||
|
padding: .3em;
|
||||||
|
min-width: 7.5em;
|
||||||
|
|
||||||
|
& .display-label {
|
||||||
|
gap: 1.5em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: .75em;
|
||||||
|
line-height: 1em;
|
||||||
|
color: rgba(0, 0, 0, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .display-value {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: .1em;
|
||||||
|
font-size: 1.3em;
|
||||||
|
line-height: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgba(0, 0, 0, .85);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1280px) {
|
||||||
|
.data-summary {
|
||||||
|
gap: .35em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-display {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1150px) {
|
||||||
|
.data-summary {
|
||||||
|
gap: .2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-display {
|
||||||
|
font-size: 12.5px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
.display_flex_container{
|
.display_flex_container{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
}
|
||||||
.display_header {
|
.display_header {
|
||||||
@ -38,37 +37,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.display_label{
|
.display {
|
||||||
font-size: 16px;
|
display: flex;
|
||||||
color: rgb(70, 70, 70);
|
justify-content: space-between;
|
||||||
text-align: left;
|
column-gap: 20px;
|
||||||
justify-content: center;
|
padding: 1px 1rem;
|
||||||
margin: 1px 0 1px 1rem;
|
align-items: stretch;
|
||||||
flex: auto;
|
|
||||||
align-items: baseline;
|
& .display_label{
|
||||||
text-overflow: ellipsis;
|
font-size: 16px;
|
||||||
overflow-x: hidden;
|
color: rgb(70, 70, 70);
|
||||||
overflow-y: hidden;
|
text-overflow: ellipsis;
|
||||||
display: -webkit-box;
|
overflow: hidden;
|
||||||
-webkit-box-orient: vertical;
|
flex-grow: 1;
|
||||||
height: 30px;
|
}
|
||||||
|
|
||||||
|
& .display_value{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgb(50, 50, 50);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.display_value{
|
@media only screen and (max-width: 1280px) {
|
||||||
font-size: 18px;
|
.display {
|
||||||
font-weight: bold;
|
padding: 1px 5px;
|
||||||
color: rgb(50, 50, 50);
|
}
|
||||||
text-align: right;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items:baseline;
|
|
||||||
margin: 1px 1rem;
|
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display_small_value{
|
.display_flex_container {
|
||||||
color: rgb(50, 50, 50);
|
flex-wrap: wrap;
|
||||||
text-align: right;
|
}
|
||||||
justify-content: center;
|
|
||||||
margin: 1px 1rem 1px 1rem;
|
|
||||||
flex: auto;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user