diff --git a/src/pages/Telemetry/Archive/ArchiveColumn.jsx b/src/pages/Telemetry/Archive/ArchiveColumn.jsx
new file mode 100644
index 0000000..6f7ebfb
--- /dev/null
+++ b/src/pages/Telemetry/Archive/ArchiveColumn.jsx
@@ -0,0 +1,31 @@
+import { memo, useMemo } from 'react'
+
+import { Grid, GridItem } from '@components/Grid'
+import { Column } from '@components/charts/Column'
+
+export const ArchiveColumn = memo(({ lineGroup, data, interval, style, headerHeight, yStart }) => {
+ const lgws = useMemo(() => lineGroup.filter(cfg => !cfg.isShape), [lineGroup])
+ const pv = useMemo(() => lgws.filter(line => line.showLabels).map(line => ({
+ color: line.color,
+ label: line.label
+ })), [lgws])
+
+ return (
+
+
+ {pv?.map((v, idx) => (
+ {v.label}
+ ))}
+
+
+
+ )
+})
+
+export default ArchiveColumn
diff --git a/src/pages/Telemetry/Archive/ArchiveDisplay.jsx b/src/pages/Telemetry/Archive/ArchiveDisplay.jsx
new file mode 100644
index 0000000..c3310ba
--- /dev/null
+++ b/src/pages/Telemetry/Archive/ArchiveDisplay.jsx
@@ -0,0 +1,66 @@
+import { memo, useMemo } from 'react'
+
+import { Grid, GridItem } from '@components/Grid'
+
+import { paramsGroups } from '../TelemetryView'
+import { ArchiveColumn } from './ArchiveColumn'
+
+const interpolationSearch = (data, begin, end, accessor) => {
+ const fy = (i) => new Date(data[i]?.[accessor] ?? 0)
+ const fx = (y, b, e) => Math.round(b + (y - fy(b)) * (e - b) / (fy(e) - fy(b)))
+ const findIdx = (val, startIdx, c) => {
+ let x = startIdx
+ let endIdx = data.length - 1
+ if(val < fy(startIdx))
+ return startIdx
+ if(val > fy(endIdx))
+ return endIdx
+ for(let i = 0; i < c; i++){
+ x = fx(val, startIdx, endIdx)
+ if(fy(x) < val)
+ startIdx = x
+ else
+ endIdx = x
+ if ((startIdx === endIdx)||(fy(startIdx) === fy(endIdx)))
+ return x
+ }
+ return x
+ }
+ let x0 = findIdx(begin, 0, 5)
+ let x1 = findIdx(end, x0, 3)
+ return { start: x0, end: x1, count: x1 - x0 }
+}
+
+export const cutData = (data, beginDate, endDate) => {
+ if (data?.length > 0) {
+ let { start, end } = interpolationSearch(data, beginDate, endDate, 'date')
+ if (start > 0) start--
+ if (end + 1 < end.length) end++
+ return data.slice(start, end)
+ }
+ return data
+}
+
+export const ArchiveDisplay = memo(({data, startDate, interval, onWheel}) => {
+ const endDate = useMemo(() => new Date(+startDate + interval), [startDate, interval])
+ const chartData = useMemo(() => cutData(data, startDate, endDate), [data, startDate, endDate])
+
+ return (
+
+ {paramsGroups.map((group, index) => (
+
+
+
+ ))}
+
+ )
+})
+
+export default ArchiveDisplay
diff --git a/src/pages/Telemetry/Archive/index.jsx b/src/pages/Telemetry/Archive/index.jsx
new file mode 100644
index 0000000..4c37e9f
--- /dev/null
+++ b/src/pages/Telemetry/Archive/index.jsx
@@ -0,0 +1,185 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import { useState, useEffect, memo, useCallback } from 'react'
+
+import { Flex } from '@components/Grid'
+import LoaderPortal from '@components/LoaderPortal'
+import { invokeWebApiWrapperAsync } from '@components/factory'
+import { DatePickerWrapper, makeDateSorter } from '@components/Table'
+import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
+import { TelemetryDataSaubService } from '@api'
+
+import { normalizeData } from '../TelemetryView'
+import { ArchiveDisplay, cutData } from './ArchiveDisplay'
+
+const DATA_COUNT = 2048 // Колличество точек на подгрузку графика
+const ADDITIVE_PAGES = 2 // Дополнительные данные для графиков
+const LOADING_TRIGGER = 0.5
+const WHEEL_SENSITIVITY = 1 / 530
+
+const getLoadingInterval = (loaded, startDate, interval) => {
+ // Если данные загружены и дата не заходит за тригер дозагрузка не требуется
+ if (
+ loaded &&
+ +startDate - interval * LOADING_TRIGGER > loaded.start &&
+ +startDate + interval * (LOADING_TRIGGER + 1) < loaded.end
+ )
+ return { loadingStartDate: startDate, newLoaded: loaded, loadingInterval: 0 }
+
+ let loadingStartDate = +startDate - interval * ADDITIVE_PAGES
+ let loadingEndDate = +startDate + interval * (ADDITIVE_PAGES + 1)
+
+ const newLoaded = {
+ start: loadingStartDate,
+ end: loadingEndDate
+ }
+
+ if (loaded) {
+ if (loadingStartDate >= loaded.start)
+ loadingStartDate = loaded.end
+ if (loadingEndDate <= loaded.end)
+ loadingEndDate = loaded.start
+ newLoaded.start = Math.min(loaded.start, loadingStartDate)
+ newLoaded.end = Math.max(loaded.end, loadingEndDate)
+ }
+
+ const loadingInterval = Math.trunc((loadingEndDate - loadingStartDate) / 1000)
+
+ return {
+ loadingStartDate: new Date(loadingStartDate),
+ newLoaded: {
+ start: new Date(newLoaded.start),
+ end: new Date(newLoaded.end)
+ },
+ loadingInterval
+ }
+}
+
+const range = (start, end) => {
+ const result = []
+ for (let i = start; i < end; i++)
+ result.push(i)
+ return result
+}
+
+export const Archive = memo(({ idWell }) => {
+ const [dataSaub, setDataSaub] = useState([])
+ const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() })
+ const [chartInterval, setChartInterval] = useState(parseInt(defaultPeriod) * 1000)
+ const [startDate, setStartDate] = useState(new Date(Date.now() - chartInterval))
+ const [showLoader, setShowLoader] = useState(false)
+ const [loaded, setLoaded] = useState(null)
+
+ const onGraphWheel = useCallback((e) => {
+ if (loaded && dateLimit.from && dateLimit.to) {
+ setStartDate((prevStartDate) => {
+ const offset = e.deltaY * chartInterval * WHEEL_SENSITIVITY
+ const nextStartDate = +prevStartDate + offset
+ const firstPossibleDate = Math.max(loaded.start, dateLimit.from)
+ const lastPossibleDate = Math.min(dateLimit.to, (loaded.end ?? Date.now())) - chartInterval
+ return new Date(Math.max(firstPossibleDate, Math.min(nextStartDate, lastPossibleDate)))
+ })
+ }
+ }, [loaded, dateLimit, chartInterval])
+
+ 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])
+
+ const isDateTimeDisabled = useCallback((date) => ({
+ disabledHours: () => range(0, 24).filter(h => {
+ if (!date) return false
+ const dt = +new Date(date).setHours(h)
+ return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
+ }),
+ disabledMinutes: () => range(0, 60).filter(m => {
+ if (!date) return false
+ const dt = +new Date(date).setMinutes(m)
+ return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
+ }),
+ disabledSeconds: () => range(0, 60).filter(s => {
+ if (!date) return false
+ const dt = +new Date(date).setSeconds(s)
+ return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
+ })
+ }), [dateLimit])
+
+ useEffect(() => invokeWebApiWrapperAsync(
+ async () => {
+ let dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
+ dates = {
+ from: new Date(dates?.from ?? 0),
+ to: new Date(dates?.to ?? 0)
+ }
+ setDateLimit(dates)
+ setStartDate(new Date(Math.max(dates.from, +dates.to - chartInterval)))
+ },
+ setShowLoader,
+ `Не удалось загрузить диапозон телеметрии для скважины "${idWell}"`,
+ 'Загрузка диапозона телеметрии'
+ ), [])
+
+ useEffect(() => {
+ setStartDate((startDate) => new Date(Math.min(Date.now() - chartInterval, startDate)))
+ setDataSaub([])
+ }, [chartInterval])
+
+ useEffect(() => {
+ if (showLoader) return
+ const { loadingStartDate, loadingInterval, newLoaded } = getLoadingInterval(loaded, startDate, chartInterval)
+ if (loadingInterval <= 0) return
+ invokeWebApiWrapperAsync(
+ async () => {
+ const data = await TelemetryDataSaubService.getData(idWell, loadingStartDate.toISOString(), loadingInterval, DATA_COUNT)
+
+ const loadedStartDate = new Date(Math.max(+newLoaded.start, +startDate - chartInterval * ADDITIVE_PAGES))
+ const loadedEndDate = new Date(Math.min(+newLoaded.end, +startDate + chartInterval * (ADDITIVE_PAGES + 1)))
+ setLoaded({ start: loadedStartDate, end: loadedEndDate })
+
+ if (data) {
+ data.forEach(elm => elm.date = new Date(elm.date))
+ setDataSaub((prevDataSaub) => {
+ const newData = [...prevDataSaub, ...normalizeData(data)]
+ newData.sort(makeDateSorter('date'))
+ return cutData(newData, loadedStartDate, loadedEndDate)
+ })
+ }
+
+ },
+ setShowLoader,
+ `Не удалось загрузить данные по скважине "${idWell}" c ${startDate.toISOString()} по ${new Date(+startDate + chartInterval).toISOString()}`,
+ 'Загрузка телеметрий в диапозоне'
+ )
+ }, [idWell, chartInterval, loaded, startDate])
+
+ return (
+ <>
+
+
+ Начальная дата:
+ setStartDate(new Date(startDate))}
+ />
+
+
+ Период:
+
setChartInterval(val * 1000)} />
+
+
+
+
+
+ >
+ )
+})
+
+export default Archive
diff --git a/src/pages/Messages.jsx b/src/pages/Telemetry/Messages.jsx
similarity index 100%
rename from src/pages/Messages.jsx
rename to src/pages/Telemetry/Messages.jsx
diff --git a/src/pages/TelemetryView/ActiveMessagesOnline.jsx b/src/pages/Telemetry/TelemetryView/ActiveMessagesOnline.jsx
similarity index 97%
rename from src/pages/TelemetryView/ActiveMessagesOnline.jsx
rename to src/pages/Telemetry/TelemetryView/ActiveMessagesOnline.jsx
index ec888ea..db5c547 100644
--- a/src/pages/TelemetryView/ActiveMessagesOnline.jsx
+++ b/src/pages/Telemetry/TelemetryView/ActiveMessagesOnline.jsx
@@ -6,7 +6,7 @@ import { invokeWebApiWrapperAsync } from '@components/factory'
import { Subscribe } from '@services/signalr'
import { MessageService } from '@api'
-import { columns } from '@pages/Messages'
+import { columns } from '../Messages'
import '@styles/message.css'
diff --git a/src/pages/TelemetryView/ChartTimeOnlineFooter.jsx b/src/pages/Telemetry/TelemetryView/ChartTimeOnlineFooter.jsx
similarity index 100%
rename from src/pages/TelemetryView/ChartTimeOnlineFooter.jsx
rename to src/pages/Telemetry/TelemetryView/ChartTimeOnlineFooter.jsx
diff --git a/src/pages/TelemetryView/CustomColumn.jsx b/src/pages/Telemetry/TelemetryView/CustomColumn.jsx
similarity index 100%
rename from src/pages/TelemetryView/CustomColumn.jsx
rename to src/pages/Telemetry/TelemetryView/CustomColumn.jsx
diff --git a/src/pages/TelemetryView/ModeDisplay.jsx b/src/pages/Telemetry/TelemetryView/ModeDisplay.jsx
similarity index 100%
rename from src/pages/TelemetryView/ModeDisplay.jsx
rename to src/pages/Telemetry/TelemetryView/ModeDisplay.jsx
diff --git a/src/pages/TelemetryView/MonitoringColumn.jsx b/src/pages/Telemetry/TelemetryView/MonitoringColumn.jsx
similarity index 100%
rename from src/pages/TelemetryView/MonitoringColumn.jsx
rename to src/pages/Telemetry/TelemetryView/MonitoringColumn.jsx
diff --git a/src/pages/TelemetryView/RigMnemo.jsx b/src/pages/Telemetry/TelemetryView/RigMnemo.jsx
similarity index 100%
rename from src/pages/TelemetryView/RigMnemo.jsx
rename to src/pages/Telemetry/TelemetryView/RigMnemo.jsx
diff --git a/src/pages/TelemetryView/SetpointSender.jsx b/src/pages/Telemetry/TelemetryView/Setpoints/SetpointSender.jsx
similarity index 85%
rename from src/pages/TelemetryView/SetpointSender.jsx
rename to src/pages/Telemetry/TelemetryView/Setpoints/SetpointSender.jsx
index 27bceae..0073b9d 100644
--- a/src/pages/TelemetryView/SetpointSender.jsx
+++ b/src/pages/Telemetry/TelemetryView/Setpoints/SetpointSender.jsx
@@ -1,4 +1,4 @@
-import { useState } from 'react'
+import { memo, useCallback, useMemo, useState } from 'react'
import { Select, Modal, Input, InputNumber } from 'antd'
import { SetpointsService } from '@api'
@@ -8,13 +8,13 @@ import { invokeWebApiWrapperAsync } from '@components/factory'
import { makeNumericRender, EditableTable } from '@components/Table'
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
-export const SetpointSender = ({ idWell, onClose, visible, setpointNames }) => {
+export const SetpointSender = memo(({ idWell, onClose, visible, setpointNames }) => {
const [expirePeriod, setExpirePeriod] = useState(defaultPeriod)
const [comment, setComment] = useState('')
const [setpoints, setSetpoints] = useState([])
const [isLoading, setIsLoading] = useState(false)
- const addingColumns = [
+ const addingColumns = useMemo(() => [
{
title: 'Наименование уставки',
dataIndex: 'name',
@@ -33,27 +33,27 @@ export const SetpointSender = ({ idWell, onClose, visible, setpointNames }) => {
render: makeNumericRender(1),
align: 'right'
}
- ]
+ ], [setpointNames])
- const onAdd = async (sp) => setSetpoints((prevSp) => {
+ const onAdd = useCallback(async (sp) => setSetpoints((prevSp) => {
sp.key = Date.now()
prevSp.push(sp)
return prevSp
- })
+ }), [])
- const onEdit = async (sp) => setSetpoints((prevSp) => {
+ const onEdit = useCallback(async (sp) => setSetpoints((prevSp) => {
const idx = prevSp.findIndex((val) => val.key === sp.key)
prevSp[idx] = sp
return prevSp
- })
+ }), [])
- const onDelete = async (sp) => setSetpoints((prevSp) => {
+ const onDelete = useCallback(async (sp) => setSetpoints((prevSp) => {
const idx = prevSp.findIndex((val) => val.key === sp.key)
prevSp.splice(idx, 1)
return prevSp
- })
+ }), [])
- const onModalOk = () => invokeWebApiWrapperAsync(
+ const onModalOk = useCallback(() => invokeWebApiWrapperAsync(
async () => {
const setpointsObject = setpoints.reduce((obj, sp) => {
obj[sp.name] = sp.value
@@ -66,12 +66,12 @@ export const SetpointSender = ({ idWell, onClose, visible, setpointNames }) => {
comment: comment
}
await SetpointsService.insert(idWell, request)
- onClose(true)
+ await onClose(true)
},
setIsLoading,
`Не удалось отправить уставки по скважине "${idWell}"`,
`Рекомендация новыой уставки`
- )
+ ), [idWell, setpoints, comment, expirePeriod, onClose])
return (
{
)
-}
+})
+
+export default SetpointSender
diff --git a/src/pages/TelemetryView/SetpointViewer.jsx b/src/pages/Telemetry/TelemetryView/Setpoints/SetpointViewer.jsx
similarity index 98%
rename from src/pages/TelemetryView/SetpointViewer.jsx
rename to src/pages/Telemetry/TelemetryView/Setpoints/SetpointViewer.jsx
index 7a0fa75..d22ccf2 100644
--- a/src/pages/TelemetryView/SetpointViewer.jsx
+++ b/src/pages/Telemetry/TelemetryView/Setpoints/SetpointViewer.jsx
@@ -72,3 +72,5 @@ export const SetpointViewer = memo(({ setpoint, visible, onClose, setpointNames
)
})
+
+export default SetpointViewer
diff --git a/src/pages/TelemetryView/Setpoints.jsx b/src/pages/Telemetry/TelemetryView/Setpoints/index.jsx
similarity index 84%
rename from src/pages/TelemetryView/Setpoints.jsx
rename to src/pages/Telemetry/TelemetryView/Setpoints/index.jsx
index a74f9e5..2339ea2 100644
--- a/src/pages/TelemetryView/Setpoints.jsx
+++ b/src/pages/Telemetry/TelemetryView/Setpoints/index.jsx
@@ -1,19 +1,19 @@
-import moment from 'moment'
import { Button, Modal } from 'antd'
-import { useState, useEffect } from 'react'
+import { useState, useEffect, memo, useCallback, useMemo } from 'react'
import { Table } from '@components/Table'
import { UserView } from '@components/views'
import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory'
+import { hasPermission } from '@utils/permissions'
import { makeStringCutter } from '@utils/string'
+import { formatDate } from '@utils'
import { SetpointsService } from '@api'
-import { SetpointSender } from './SetpointSender'
+import SetpointSender from './SetpointSender'
import { SetpointViewer, getSetpointStatus } from './SetpointViewer'
-import { hasPermission } from '@asb/utils/permissions'
-export const Setpoints = ({ idWell, ...other }) => {
+export const Setpoints = memo(({ idWell, ...other }) => {
const [isModalVisible, setIsModalVisible] = useState(false)
const [isSenderVisible, setIsSenderVisible] = useState(false)
const [isViewerVisible, setIsViewerVisible] = useState(false)
@@ -37,21 +37,21 @@ export const Setpoints = ({ idWell, ...other }) => {
'Получение списка имён уставок'
), [idWell])
- const showMore = (id) => {
+ const showMore = useCallback((id) => {
const selected = setpoints.find((sp) => sp.id === id)
setSelected(selected ?? {})
setIsViewerVisible(true)
- }
+ }, [setpoints])
- const historyColumns = [
- { title: 'Дата', dataIndex: 'uploadDate', render: item => moment(item).format('DD MMM YYYY, HH:mm:ss') },
+ const historyColumns = useMemo(() => [
+ { title: 'Дата', dataIndex: 'uploadDate', render: item => formatDate(item) },
{ title: 'Автор', dataIndex: 'author', render: (user) => },
{ title: 'Комментарий', dataIndex: 'comment', render: makeStringCutter() },
{ title: 'Статус', dataIndex: 'idState', render: (id) => getSetpointStatus(parseInt(id)) },
{ dataIndex: 'id', render: (id) => },
- ]
+ ], [showMore])
- const updateTable = async () => await invokeWebApiWrapperAsync(
+ const updateTable = useCallback(async () => await invokeWebApiWrapperAsync(
async () => {
const setpoints = await SetpointsService.getByIdWell(idWell)
setSetpoints(setpoints)
@@ -59,19 +59,19 @@ export const Setpoints = ({ idWell, ...other }) => {
setIsLoading,
`Не удалось загрузить список для скважины "${idWell}"`,
'Получение списка скважин'
- )
+ ), [idWell])
- const onOpenClick = async () => {
+ const onOpenClick = useCallback(async () => {
await updateTable()
setIsModalVisible(true)
- }
+ }, [updateTable])
- const onSenderClose = (pushed) => {
- if (pushed) updateTable()
+ const onSenderClose = useCallback(async (pushed) => {
+ if (pushed) await updateTable()
setIsSenderVisible(false)
- }
+ }, [updateTable])
- return hasPermission('Setpoints.get') && (
+ return !hasPermission('Setpoints.get') ? null : (
)
-}
+})
+
+export default Setpoints
diff --git a/src/pages/TelemetryView/UserOfWells.jsx b/src/pages/Telemetry/TelemetryView/UserOfWells.jsx
similarity index 100%
rename from src/pages/TelemetryView/UserOfWells.jsx
rename to src/pages/Telemetry/TelemetryView/UserOfWells.jsx
diff --git a/src/pages/TelemetryView/index.jsx b/src/pages/Telemetry/TelemetryView/index.jsx
similarity index 100%
rename from src/pages/TelemetryView/index.jsx
rename to src/pages/Telemetry/TelemetryView/index.jsx
diff --git a/src/pages/Telemetry/index.jsx b/src/pages/Telemetry/index.jsx
new file mode 100644
index 0000000..551cbfb
--- /dev/null
+++ b/src/pages/Telemetry/index.jsx
@@ -0,0 +1,52 @@
+import { Switch, useParams } from 'react-router-dom'
+import { memo, useMemo } from 'react'
+import { Layout, Menu } from 'antd'
+import { AlertOutlined, FundViewOutlined, DatabaseOutlined } from '@ant-design/icons'
+
+import { PrivateRoute, PrivateDefaultRoute, PrivateMenuItem } from '@components/Private'
+
+import Archive from './Archive'
+import Messages from './Messages'
+import TelemetryView from './TelemetryView'
+
+import '@styles/index.css'
+
+const { Content } = Layout
+
+export const Telemetry = memo(({ idWell }) => {
+ const { tab } = useParams()
+ const rootPath = useMemo(() => `/well/${idWell}/telemetry`, [idWell])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+})
+
+export default Telemetry
diff --git a/src/pages/Well.jsx b/src/pages/Well.jsx
index 5df9768..8c4aab8 100644
--- a/src/pages/Well.jsx
+++ b/src/pages/Well.jsx
@@ -2,9 +2,7 @@ import { memo, useMemo } from 'react'
import {
FolderOutlined,
FundViewOutlined,
- AlertOutlined,
FilePdfOutlined,
- DatabaseOutlined,
ExperimentOutlined,
DeploymentUnitOutlined,
} from '@ant-design/icons'
@@ -14,12 +12,10 @@ import { Switch, useParams } from 'react-router-dom'
import { PrivateRoute, PrivateDefaultRoute, PrivateMenuItem } from '@components/Private'
import Report from './Report'
-import Archive from './Archive'
import Measure from './Measure'
-import Messages from './Messages'
import Analytics from './Analytics'
import Documents from './Documents'
-import TelemetryView from './TelemetryView'
+import Telemetry from './Telemetry'
import WellOperations from './WellOperations'
import DrillingProgram from './DrillingProgram'
import TelemetryAnalysis from './TelemetryAnalysis'
@@ -35,12 +31,10 @@ export const Well = memo(() => {
return (