forked from ddrilling/asb_cloud_front
Удалены старые страницы, использующие TelemetryAnalytics
This commit is contained in:
parent
f0659f234c
commit
9e8648d33e
@ -1,163 +0,0 @@
|
|||||||
import { Table, Select, DatePicker } from "antd";
|
|
||||||
import { TelemetryAnalyticsService } from "../src/services/api";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import notify from "../src/components/notify";
|
|
||||||
import LoaderPortal from "../src/components/LoaderPortal";
|
|
||||||
import moment from "moment";
|
|
||||||
import "../styles/message.css";
|
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
const pageSize = 26;
|
|
||||||
const { RangePicker } = DatePicker;
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: "Название операции",
|
|
||||||
key: "name",
|
|
||||||
dataIndex: "name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Дата начала операции",
|
|
||||||
key: "beginDate",
|
|
||||||
dataIndex: "beginDate",
|
|
||||||
render: (item) => moment.utc(item).local().format("DD MMM YYYY, HH:mm:ss"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Дата окончания операции",
|
|
||||||
key: "endDate",
|
|
||||||
dataIndex: "endDate",
|
|
||||||
render: (item) => moment.utc(item).local().format("DD MMM YYYY, HH:mm:ss"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Глубина скважины в начале операции",
|
|
||||||
key: "beginWellDepth",
|
|
||||||
dataIndex: "startWellDepth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Глубина скважины в конце операции",
|
|
||||||
key: "endWellDepth",
|
|
||||||
dataIndex: "endWellDepth",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const filterOptions = [
|
|
||||||
{ label: "Невозможно определить операцию", value: 1 },
|
|
||||||
{ label: "Роторное бурение", value: 2 },
|
|
||||||
{ label: "Слайдирование", value: 3 },
|
|
||||||
{ label: "Подъем с проработкой", value: 4 },
|
|
||||||
{ label: "Спуск с проработкой", value: 5 },
|
|
||||||
{ label: "Подъем с промывкой", value: 6 },
|
|
||||||
{ label: "Спуск с промывкой", value: 7 },
|
|
||||||
{ label: "Спуск в скважину", value: 8 },
|
|
||||||
{ label: "Спуск с вращением", value: 9 },
|
|
||||||
{ label: "Подъем из скважины", value: 10 },
|
|
||||||
{ label: "Подъем с вращением", value: 11 },
|
|
||||||
{ label: "Промывка в покое", value: 12 },
|
|
||||||
{ label: "Промывка с вращением", value: 13 },
|
|
||||||
{ label: "Удержание в клиньях", value: 14 },
|
|
||||||
{ label: "Неподвижное состояние", value: 15 },
|
|
||||||
{ label: "Вращение без циркуляции", value: 16 },
|
|
||||||
{ label: "На поверхности", value: 17 },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function WellTelemetryAnalysis() {
|
|
||||||
let { id } = useParams();
|
|
||||||
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const [range, setRange] = useState([]);
|
|
||||||
const [categories, setCategories] = useState([]);
|
|
||||||
const [pagination, setPagination] = useState(null);
|
|
||||||
const [operations, setOperations] = useState([]);
|
|
||||||
|
|
||||||
const [loader, setLoader] = useState(false);
|
|
||||||
|
|
||||||
const children = filterOptions.map((line) => (
|
|
||||||
<Option key={line.value}>{line.label}</Option>
|
|
||||||
));
|
|
||||||
|
|
||||||
const onChangeRange = (range) => {
|
|
||||||
setRange(range);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const GetOperations = async () => {
|
|
||||||
setLoader(true);
|
|
||||||
try {
|
|
||||||
let begin = null;
|
|
||||||
let end = null;
|
|
||||||
if (range?.length > 1) {
|
|
||||||
begin = range[0].toISOString();
|
|
||||||
end = range[1].toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
await TelemetryAnalyticsService.getOperationsByWell(
|
|
||||||
`${id}`,
|
|
||||||
(page - 1) * pageSize,
|
|
||||||
pageSize,
|
|
||||||
categories,
|
|
||||||
begin,
|
|
||||||
end
|
|
||||||
).then((paginatedOperations) => {
|
|
||||||
setOperations(
|
|
||||||
paginatedOperations?.items.map((o) => {
|
|
||||||
return {
|
|
||||||
key: o.id,
|
|
||||||
begin: o.date,
|
|
||||||
...o,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setPagination({
|
|
||||||
total: paginatedOperations?.count,
|
|
||||||
current: Math.floor(paginatedOperations?.skip / pageSize),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (ex) {
|
|
||||||
notify(`Не удалось загрузить операции по скважине "${id}"`, "error");
|
|
||||||
console.log(ex);
|
|
||||||
}
|
|
||||||
setLoader(false);
|
|
||||||
};
|
|
||||||
GetOperations();
|
|
||||||
}, [id, categories, range, page]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="filter-group">
|
|
||||||
<h3 className="filter-group-heading">Фильтр операций</h3>
|
|
||||||
<Select
|
|
||||||
mode="multiple"
|
|
||||||
allowClear
|
|
||||||
placeholder="Фильтр операций"
|
|
||||||
className="filter-selector"
|
|
||||||
value={categories}
|
|
||||||
onChange={setCategories}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Select>
|
|
||||||
<RangePicker
|
|
||||||
showTime
|
|
||||||
placeholder={["Дата начала операции", "Дата окончания операции"]}
|
|
||||||
onChange={onChangeRange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<LoaderPortal show={loader}>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
dataSource={operations}
|
|
||||||
size={"small"}
|
|
||||||
pagination={{
|
|
||||||
pageSize: pageSize,
|
|
||||||
showSizeChanger: false,
|
|
||||||
total: pagination?.total,
|
|
||||||
current: page,
|
|
||||||
onChange: (page) => setPage(page),
|
|
||||||
}}
|
|
||||||
rowKey={(record) => record.id}
|
|
||||||
/>
|
|
||||||
</LoaderPortal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
import moment from 'moment'
|
|
||||||
import { DatePicker } from 'antd'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { TelemetryAnalyticsService } from '@api'
|
|
||||||
import { ChartOperationTime } from './charts/ChartOperationTime'
|
|
||||||
|
|
||||||
const { RangePicker } = DatePicker
|
|
||||||
|
|
||||||
const lines = [{ labelAccessorName: 'processName', pieceAccessorName: 'duration' }]
|
|
||||||
|
|
||||||
export const AnalysisOperationTime = () => {
|
|
||||||
const { id } = useParams()
|
|
||||||
const [operationTimeData, setOperationTimeData] = useState([])
|
|
||||||
const [loader, setLoader] = useState(false)
|
|
||||||
const [range, setRange] = useState([moment().subtract(1,'days'), moment()])
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const begin = range?.length > 1 ? range[0].toISOString() : null
|
|
||||||
const end = range?.length > 1 ? range[1].toISOString() : null
|
|
||||||
const summary = await TelemetryAnalyticsService.getOperationsSummary(id, begin, end)
|
|
||||||
setOperationTimeData(summary)
|
|
||||||
},
|
|
||||||
setLoader,
|
|
||||||
`Не удалось получить данные для анализа Операция-Время по скважине '${id}' за период с ${begin} по ${end}`,
|
|
||||||
'Получение данных для анализа Операция-Время по скважине'
|
|
||||||
), [id, range])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoaderPortal show={loader}>
|
|
||||||
<RangePicker showTime onChange={(range) => setRange(range)} />
|
|
||||||
<ChartOperationTime data={operationTimeData} lines={lines} />
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnalysisOperationTime
|
|
@ -1,68 +0,0 @@
|
|||||||
import moment from 'moment'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import { ChartOpertationTimeBase } from './ChartOperationTimeBase'
|
|
||||||
|
|
||||||
export const CreateLabels = () => {
|
|
||||||
let labels = []
|
|
||||||
return labels
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreateData = (lineConfig) => {
|
|
||||||
let datasets = {
|
|
||||||
label: lineConfig.label,
|
|
||||||
data: [],
|
|
||||||
backgroundColor: [
|
|
||||||
'#f00', '#ff0', '#f0f', '#0ff', '#00f', '#0f0'
|
|
||||||
],
|
|
||||||
}
|
|
||||||
return datasets
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChartOperationTime = ({ lines, data, rangeDate }) => {
|
|
||||||
const [opertationTimeDataParams, setOpertationTimeDataParams] = useState({ data: { labels: [], datasets: [] } })
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if ((!lines)
|
|
||||||
|| (!data))
|
|
||||||
return
|
|
||||||
|
|
||||||
let newLabels = lines.map(lineCfg => {
|
|
||||||
let labels = CreateLabels(lineCfg)
|
|
||||||
if (data.length !== 0)
|
|
||||||
labels = data.map(dataItem => {
|
|
||||||
return dataItem[lineCfg.labelAccessorName]
|
|
||||||
})
|
|
||||||
return labels
|
|
||||||
})
|
|
||||||
|
|
||||||
let newDatasets = lines.map(lineCfg => {
|
|
||||||
let datasets = CreateData(lineCfg)
|
|
||||||
if (data.length !== 0)
|
|
||||||
datasets.data = data.map(dataItem => {
|
|
||||||
return dataItem[lineCfg.pieceAccessorName]
|
|
||||||
})
|
|
||||||
return datasets
|
|
||||||
})
|
|
||||||
|
|
||||||
let interval = rangeDate ? (rangeDate[1] - rangeDate[0]) / 1000 : null
|
|
||||||
let startDate = rangeDate ? rangeDate[0] : moment()
|
|
||||||
let newParams = {
|
|
||||||
xInterval: interval,
|
|
||||||
xStart: startDate,
|
|
||||||
data: {
|
|
||||||
labels: newLabels,
|
|
||||||
datasets: newDatasets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setOpertationTimeDataParams(newParams)
|
|
||||||
|
|
||||||
console.log(newParams)
|
|
||||||
|
|
||||||
}, [data, lines, rangeDate])
|
|
||||||
|
|
||||||
return (<>
|
|
||||||
<ChartOpertationTimeBase dataParams={opertationTimeDataParams} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import {
|
|
||||||
Chart,
|
|
||||||
ArcElement,
|
|
||||||
TimeScale,
|
|
||||||
Legend,
|
|
||||||
PointElement,
|
|
||||||
ChartData,
|
|
||||||
ChartTypeRegistry,
|
|
||||||
ChartOptions,
|
|
||||||
DoughnutController,
|
|
||||||
} from 'chart.js'
|
|
||||||
import 'chartjs-adapter-moment'
|
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
|
||||||
|
|
||||||
Chart.register(TimeScale, DoughnutController, PointElement, ArcElement, Legend, ChartDataLabels)
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
responsive: true,
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
position: 'top',
|
|
||||||
text: 'Doughnut Chart',
|
|
||||||
fontSize: 18,
|
|
||||||
fontColor: '#111'
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: true,
|
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
fontColor: '#333',
|
|
||||||
fontSize: 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
|
|
||||||
x: String
|
|
||||||
label: number
|
|
||||||
y: number
|
|
||||||
}[], unknown>
|
|
||||||
|
|
||||||
export type ChartTimeDataParams = {
|
|
||||||
data: ChartTimeData,
|
|
||||||
xStart?: Date,
|
|
||||||
xInterval?: number,
|
|
||||||
displayLabels?: Boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ChartTimeBaseProps = {
|
|
||||||
dataParams: ChartTimeDataParams,
|
|
||||||
// TODO: Create good type for options
|
|
||||||
options?: ChartOptions<keyof ChartTypeRegistry> | any,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TimeParams = {
|
|
||||||
unit: String
|
|
||||||
stepSize: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const linesPerInterval = 32
|
|
||||||
|
|
||||||
export const timeUnitByInterval = (intervalSec: number): String => {
|
|
||||||
if (intervalSec <= 60)
|
|
||||||
return 'millisecond'
|
|
||||||
|
|
||||||
if (intervalSec <= 32 * 60)
|
|
||||||
return 'second'
|
|
||||||
|
|
||||||
if (intervalSec <= 32 * 60 * 60)
|
|
||||||
return 'minute'
|
|
||||||
|
|
||||||
if (intervalSec <= 32 * 12 * 60 * 60)
|
|
||||||
return 'hour'
|
|
||||||
|
|
||||||
if (intervalSec <= 32 * 24 * 60 * 60)
|
|
||||||
return 'day'
|
|
||||||
|
|
||||||
if (intervalSec <= 32 * 7 * 24 * 60 * 60)
|
|
||||||
return 'week'
|
|
||||||
|
|
||||||
if (intervalSec <= 32 * 30.4375 * 24 * 60 * 60)
|
|
||||||
return 'month'
|
|
||||||
|
|
||||||
if (intervalSec <= 32 * 121.75 * 24 * 60 * 60)
|
|
||||||
return 'quarter'
|
|
||||||
else
|
|
||||||
return 'year'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const timeParamsByInterval = (intervalSec: number): TimeParams => {
|
|
||||||
let stepSize = intervalSec
|
|
||||||
let unit = timeUnitByInterval(intervalSec)
|
|
||||||
|
|
||||||
switch (unit) {
|
|
||||||
case 'millisecond':
|
|
||||||
stepSize *= 1000
|
|
||||||
break
|
|
||||||
case 'second':
|
|
||||||
//stepSize *= 1
|
|
||||||
break
|
|
||||||
case 'minute':
|
|
||||||
stepSize /= 60
|
|
||||||
break
|
|
||||||
case 'hour':
|
|
||||||
stepSize /= 60 * 60
|
|
||||||
break
|
|
||||||
case 'day':
|
|
||||||
stepSize /= 24 * 60 * 60
|
|
||||||
break
|
|
||||||
case 'week':
|
|
||||||
stepSize /= 7 * 24 * 60 * 60
|
|
||||||
break
|
|
||||||
case 'month':
|
|
||||||
stepSize /= 30 * 24 * 60 * 60
|
|
||||||
break
|
|
||||||
case 'quarter':
|
|
||||||
stepSize /= 91 * 24 * 60 * 60
|
|
||||||
break
|
|
||||||
case 'year':
|
|
||||||
stepSize /= 365.25 * 24 * 60 * 60
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
stepSize = Math.round(stepSize / linesPerInterval)
|
|
||||||
stepSize = stepSize > 0 ? stepSize : 1
|
|
||||||
return { unit, stepSize }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChartOpertationTimeBase: React.FC<ChartTimeBaseProps> = ({ options, dataParams }) => {
|
|
||||||
const chartRef = useRef<HTMLCanvasElement>(null)
|
|
||||||
const [chart, setChart] = useState<any>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if ((chartRef.current) && (!chart)) {
|
|
||||||
let thisOptions = {}
|
|
||||||
Object.assign(thisOptions, defaultOptions, options)
|
|
||||||
|
|
||||||
let newChart = new Chart(chartRef.current, {
|
|
||||||
type: 'doughnut',
|
|
||||||
plugins: [ChartDataLabels],
|
|
||||||
options: thisOptions,
|
|
||||||
data: dataParams.data
|
|
||||||
})
|
|
||||||
setChart(newChart)
|
|
||||||
|
|
||||||
return () => chart?.destroy()
|
|
||||||
}
|
|
||||||
}, [chart, options, dataParams])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!chart)
|
|
||||||
return
|
|
||||||
|
|
||||||
chart.data = dataParams.data
|
|
||||||
chart.options.aspectRatio = options?.aspectRatio
|
|
||||||
if (dataParams.xStart) {
|
|
||||||
let interval = Number(dataParams.xInterval ?? 600)
|
|
||||||
let start = new Date(dataParams.xStart)
|
|
||||||
let end = new Date(dataParams.xStart)
|
|
||||||
end.setSeconds(end.getSeconds() + interval)
|
|
||||||
let { unit, stepSize } = timeParamsByInterval(interval)
|
|
||||||
|
|
||||||
if (chart.options.scales?.x) {
|
|
||||||
chart.options.scales.x.max = end.getTime()
|
|
||||||
chart.options.scales.x.min = start.getTime()
|
|
||||||
chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true
|
|
||||||
chart.options.scales.x.time.unit = unit
|
|
||||||
chart.options.scales.x.time.stepSize = stepSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chart.update()
|
|
||||||
}, [chart, dataParams, options])
|
|
||||||
|
|
||||||
return (<canvas ref={chartRef} />)
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import {
|
|
||||||
Chart,
|
|
||||||
TimeScale,
|
|
||||||
LinearScale,
|
|
||||||
Legend,
|
|
||||||
LineController,
|
|
||||||
PointElement,
|
|
||||||
LineElement
|
|
||||||
} from 'chart.js'
|
|
||||||
import 'chartjs-adapter-moment'
|
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
|
||||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
|
||||||
|
|
||||||
Chart.register(TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin)
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
responsive: true,
|
|
||||||
aspectRatio: 2.45,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'hour',
|
|
||||||
displayFormats: {
|
|
||||||
hour: 'MM.DD'
|
|
||||||
},
|
|
||||||
tooltipFormat: 'DD T'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: 'linear',
|
|
||||||
position: 'top',
|
|
||||||
reverse: true,
|
|
||||||
// ticks: {
|
|
||||||
// // forces step size to be 50 units
|
|
||||||
// stepSize: 50
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parsing: {
|
|
||||||
xAxisKey: 'date',
|
|
||||||
yAxisKey: 'depth'
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
point: {
|
|
||||||
radius: 1.7,
|
|
||||||
hoverRadius: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
datalabels: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeDataset = (data, label, color, width = 1.5, dash) => ({
|
|
||||||
label: label,
|
|
||||||
data: data,
|
|
||||||
backgroundColor: color,
|
|
||||||
borderColor: color,
|
|
||||||
borderWidth: width,
|
|
||||||
borderDash: dash,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const ChartTelemetryDepthToDay = ({ depthData, bitPositionData }) => {
|
|
||||||
const chartRef = useRef(null)
|
|
||||||
const [chart, setChart] = useState()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const data = {
|
|
||||||
datasets: [
|
|
||||||
makeDataset(depthData, 'Глубина', '#0A0'),
|
|
||||||
makeDataset(bitPositionData, 'Положение долота', 'blue'),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if(chartRef.current && !chart) {
|
|
||||||
const thisOptions = {}
|
|
||||||
Object.assign(thisOptions, defaultOptions)
|
|
||||||
|
|
||||||
const newChart = new Chart(chartRef.current, {
|
|
||||||
type: 'line',
|
|
||||||
plugins: [ChartDataLabels],
|
|
||||||
options: thisOptions,
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
setChart(newChart)
|
|
||||||
|
|
||||||
return () => chart?.destroy()
|
|
||||||
} else {
|
|
||||||
chart.data = data
|
|
||||||
chart.update()
|
|
||||||
}
|
|
||||||
}, [chart, depthData, bitPositionData])
|
|
||||||
|
|
||||||
return(<canvas ref={chartRef} />)
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import { Chart, registerables } from 'chart.js'
|
|
||||||
|
|
||||||
Chart.register(...registerables)
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
responsive: true,
|
|
||||||
aspectRatio: 2.6,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Дата начала интервала'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Коэффициент скорости'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
datalabels: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChartTelemetryDepthToInterval = ({ depthToIntervalData }) => {
|
|
||||||
const chartRef = useRef(null)
|
|
||||||
const [chart, setChart] = useState()
|
|
||||||
|
|
||||||
const calculateBarWidth = (dataLength) => {
|
|
||||||
if (dataLength < 3) return 150
|
|
||||||
if (dataLength < 16) return 70
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const xData = depthToIntervalData.map(el => new Date(el.intervalStartDate).toLocaleString())
|
|
||||||
const yData = depthToIntervalData.map(el => el.intervalDepthProgress.toFixed(3))
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
labels: xData,
|
|
||||||
datasets: [{
|
|
||||||
label: 'Скорость проходки за интервал',
|
|
||||||
data: yData,
|
|
||||||
borderColor: '#00b300',
|
|
||||||
borderWidth: 2,
|
|
||||||
backgroundColor: '#0A0',
|
|
||||||
barThickness: calculateBarWidth(xData.length)
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
const thisOptions = {}
|
|
||||||
Object.assign(thisOptions, defaultOptions)
|
|
||||||
|
|
||||||
if (chartRef.current && !chart) {
|
|
||||||
const newChart = new Chart(chartRef.current, {
|
|
||||||
type: 'bar',
|
|
||||||
options: thisOptions,
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
setChart(newChart)
|
|
||||||
} else {
|
|
||||||
chart.data = data
|
|
||||||
chart.options = thisOptions
|
|
||||||
chart.update()
|
|
||||||
}
|
|
||||||
}, [chart, depthToIntervalData])
|
|
||||||
|
|
||||||
return (<canvas ref={chartRef} />)
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import { Chart, registerables } from 'chart.js'
|
|
||||||
|
|
||||||
Chart.register(...registerables)
|
|
||||||
|
|
||||||
const transformSecondsToHoursString = (seconds) => {
|
|
||||||
const hours = Math.floor(seconds / 3600)
|
|
||||||
const minutes = Math.floor((seconds % 3600) / 60)
|
|
||||||
const s = seconds - (hours * 3600) - (minutes * 60)
|
|
||||||
|
|
||||||
return `${hours} ч.${minutes} мин.${s} сек.`
|
|
||||||
}
|
|
||||||
|
|
||||||
const transformSecondsToTimeString = (seconds) => {
|
|
||||||
if (seconds === 1) // 1 is default state if null returned (1 is to show chart anyway with 0 sec)
|
|
||||||
return '0 сек.'
|
|
||||||
else if(seconds < 60)
|
|
||||||
return seconds + ' сек.'
|
|
||||||
else if (seconds < 3600)
|
|
||||||
return Math.floor(seconds / 60) + ' мин. ' + (0.6 * (seconds % 60)).toFixed() + ' сек.'
|
|
||||||
else
|
|
||||||
return transformSecondsToHoursString(seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
responsive: true,
|
|
||||||
aspectRatio: 2.8,
|
|
||||||
plugins: {
|
|
||||||
datalabels: {
|
|
||||||
color: '#ffffff',
|
|
||||||
formatter: transformSecondsToTimeString,
|
|
||||||
font: {
|
|
||||||
weight: 'bold'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: (tooltipItem) => transformSecondsToTimeString(tooltipItem.parsed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const chartPartsColors = [
|
|
||||||
'#54a60c', '#0ca68a', '#0c8aa6', '#0c57a6', '#0c33a6',
|
|
||||||
'#6f10d5', '#d510a1', '#f1bc41', '#c5f141', '#41f196',
|
|
||||||
'#41cbf1', '#4196f1', '#bf41f1', '#41f1c5', '#cbf141',
|
|
||||||
'#f1ce41', '#f17f41', '#f14141', '#34b40e', '#420eb4'
|
|
||||||
]
|
|
||||||
|
|
||||||
export const ChartTelemetryOperationsSummary = ({ operationsData }) => {
|
|
||||||
const chartRef = useRef(null)
|
|
||||||
const [chart, setChart] = useState()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const namesData = operationsData?.map(el => el.operationName) ?? ['Нет операций']
|
|
||||||
const durationsData = operationsData?.map(el => el.duration) ?? [1]
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
labels: namesData,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Скорость проходки за интервал',
|
|
||||||
data: durationsData,
|
|
||||||
borderColor: chartPartsColors,
|
|
||||||
backgroundColor: chartPartsColors,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const thisOptions = {}
|
|
||||||
Object.assign(thisOptions, defaultOptions)
|
|
||||||
|
|
||||||
if (chartRef.current && !chart) {
|
|
||||||
const newChart = new Chart(chartRef.current, {
|
|
||||||
type: 'doughnut',
|
|
||||||
options: thisOptions,
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
setChart(newChart)
|
|
||||||
} else {
|
|
||||||
chart.data = data
|
|
||||||
chart.options = thisOptions
|
|
||||||
chart.update()
|
|
||||||
}
|
|
||||||
}, [chart, operationsData])
|
|
||||||
|
|
||||||
return (<canvas ref={chartRef} />)
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import { useState, useEffect, memo, useContext } from 'react'
|
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { ChartTelemetryDepthToDay } from '@components/charts/ChartTelemetryDepthToDay'
|
|
||||||
import { TelemetryAnalyticsService } from '@api'
|
|
||||||
|
|
||||||
|
|
||||||
export const TelemetryAnalysisDepthToDay = memo(() => {
|
|
||||||
const [depthData, setDepthData] = useState([])
|
|
||||||
const [bitPositionData, setBitPositionData] = useState([])
|
|
||||||
const [loader, setLoader] = useState(false)
|
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const depthToDayData = await TelemetryAnalyticsService.getWellDepthToDay(idWell)
|
|
||||||
|
|
||||||
const depths = depthToDayData.map(el => ({depth: el.wellDepth, date: el.date}))
|
|
||||||
.sort((a, b) => new Date(a.date) - new Date(b.date))
|
|
||||||
setDepthData(depths)
|
|
||||||
|
|
||||||
const bitPositions = depthToDayData.map(el => ({depth: el.bitDepth, date: el.date}))
|
|
||||||
.sort((a, b) => new Date(a.date) - new Date(b.date))
|
|
||||||
setBitPositionData(bitPositions)
|
|
||||||
},
|
|
||||||
setLoader,
|
|
||||||
`Не удалось получить данные для анализа Глубина-День по скважине "${idWell}"`,
|
|
||||||
'Получение данных для анализа Глубина-День'
|
|
||||||
), [idWell])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoaderPortal show={loader}>
|
|
||||||
<div className={'mt-20px'}>
|
|
||||||
<ChartTelemetryDepthToDay
|
|
||||||
depthData={depthData}
|
|
||||||
bitPositionData={bitPositionData}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TelemetryAnalysisDepthToDay
|
|
@ -1,53 +0,0 @@
|
|||||||
import { Select } from 'antd'
|
|
||||||
import { useState, useEffect, memo, useContext } from 'react'
|
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { ChartTelemetryDepthToInterval } from '@components/charts/ChartTelemetryDepthToInterval'
|
|
||||||
import { TelemetryAnalyticsService } from '@api'
|
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
|
|
||||||
|
|
||||||
const timePeriodCollection = [
|
|
||||||
{ value: '3600', label: '1 час' },
|
|
||||||
{ value: '21600', label: '6 часов' },
|
|
||||||
{ value: '43200', label: '12 часов' },
|
|
||||||
{ value: '86400', label: '24 часа' }
|
|
||||||
]
|
|
||||||
|
|
||||||
export const TelemetryAnalysisDepthToInterval = memo(() => {
|
|
||||||
const [depthToIntervalData, setDepthToIntervalData] = useState([])
|
|
||||||
const [chartInterval, setChartInterval] = useState(86400)
|
|
||||||
const [loader, setLoader] = useState(false)
|
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const depthToIntervalData = await TelemetryAnalyticsService.getWellDepthToInterval(idWell, chartInterval)
|
|
||||||
setDepthToIntervalData(arrayOrDefault(depthToIntervalData))
|
|
||||||
},
|
|
||||||
setLoader,
|
|
||||||
`Не удалось получить данные для анализа скорость проходки-интервал "${idWell}"`,
|
|
||||||
'Получение данных для анализа скорость проходки-интервал'
|
|
||||||
), [idWell, chartInterval])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoaderPortal show={loader}>
|
|
||||||
<div className={'mt-20px'}>
|
|
||||||
<Select
|
|
||||||
options={timePeriodCollection}
|
|
||||||
onChange={setChartInterval}
|
|
||||||
defaultValue={'86400'}
|
|
||||||
className={'ml-30px'}
|
|
||||||
/>
|
|
||||||
<ChartTelemetryDepthToInterval
|
|
||||||
depthToIntervalData={depthToIntervalData}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TelemetryAnalysisDepthToInterval
|
|
@ -1,72 +0,0 @@
|
|||||||
import moment from 'moment'
|
|
||||||
import { Form, DatePicker } from 'antd'
|
|
||||||
import { useState, useEffect, memo, useContext } from 'react'
|
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { ChartTelemetryOperationsSummary } from '@components/charts/ChartTelemetryOperationsSummary'
|
|
||||||
import { TelemetryAnalyticsService } from '@api'
|
|
||||||
|
|
||||||
|
|
||||||
const { RangePicker } = DatePicker
|
|
||||||
|
|
||||||
export const TelemetryAnalysisOperationsSummary = memo(() => {
|
|
||||||
const [avilableDatesRange, setAviableDatesRange] = useState([moment(),moment()])
|
|
||||||
const [filterDateRange, setFilterDateRange] = useState([moment().subtract(1, 'days'),moment()])
|
|
||||||
const [operationsData, setOperationsData] = useState([])
|
|
||||||
const [loader, setLoader] = useState(false)
|
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
|
||||||
|
|
||||||
const disabledDate = (current) => current < avilableDatesRange[0] || current > avilableDatesRange[1]
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
|
||||||
async() => {
|
|
||||||
const datesRange = await TelemetryAnalyticsService.getOperationsDateRange(idWell)
|
|
||||||
setAviableDatesRange(datesRange)
|
|
||||||
},
|
|
||||||
setLoader,
|
|
||||||
`Не удалось загрузить диапозон дат для скважины "${idWell}"`,
|
|
||||||
'Получение диапозона дат телеметрии скважины'
|
|
||||||
), [idWell])
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const operationsSummaryData = await TelemetryAnalyticsService.getOperationsSummary(idWell,
|
|
||||||
filterDateRange[0].toISOString(), filterDateRange[1].toISOString())
|
|
||||||
|
|
||||||
setOperationsData(operationsSummaryData)
|
|
||||||
},
|
|
||||||
setLoader,
|
|
||||||
`Не удалось получить данные для анализа операций по скважине "${idWell}"`,
|
|
||||||
'Получение данных для анализа операций по скважине'
|
|
||||||
), [idWell, filterDateRange])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoaderPortal show={loader}>
|
|
||||||
<div className={'w-100 mt-20px'}>
|
|
||||||
<Form layout={'vertical'} name={'operationsForm'} initialValues={{ remember: true }}>
|
|
||||||
<div className={'d-flex'}>
|
|
||||||
<Form.Item
|
|
||||||
name={'period'}
|
|
||||||
className={'ml-30px'}
|
|
||||||
initialValue={filterDateRange}
|
|
||||||
label={'Диапазон дат графика:'}
|
|
||||||
>
|
|
||||||
<RangePicker
|
|
||||||
showTime
|
|
||||||
allowClear={false}
|
|
||||||
disabledDate={disabledDate}
|
|
||||||
onCalendarChange={(dates) => setFilterDateRange(dates)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
<ChartTelemetryOperationsSummary operationsData={operationsData}/>
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TelemetryAnalysisOperationsSummary
|
|
@ -1,5 +0,0 @@
|
|||||||
import { memo } from 'react'
|
|
||||||
|
|
||||||
export const TelemetryAnalysisOperationsToInterval = memo(() => (<>123</>))
|
|
||||||
|
|
||||||
export default TelemetryAnalysisOperationsToInterval
|
|
@ -1,44 +0,0 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import { memo, useContext, useMemo } from 'react'
|
|
||||||
import { FolderOutlined } from '@ant-design/icons'
|
|
||||||
import { Layout } from 'antd'
|
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
|
||||||
|
|
||||||
import TelemetryAnalysisDepthToDay from './TelemetryAnalysisDepthToDay'
|
|
||||||
import TelemetryAnalysisDepthToInterval from './TelemetryAnalysisDepthToInterval'
|
|
||||||
import TelemetryAnalysisOperationsSummary from './TelemetryAnalysisOperationsSummary'
|
|
||||||
import TelemetryAnalysisOperationsToInterval from './TelemetryAnalysisOperationsToInterval'
|
|
||||||
|
|
||||||
const { Content } = Layout
|
|
||||||
|
|
||||||
export const TelemetryAnalysis = memo(() => {
|
|
||||||
const { tab } = useParams()
|
|
||||||
const root = useContext(RootPathContext)
|
|
||||||
const rootPath = useMemo(() => `${root}/telemetryAnalysis`, [root])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RootPathContext.Provider value={rootPath}>
|
|
||||||
<PrivateMenu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
|
|
||||||
<PrivateMenu.Link icon={<FolderOutlined />} key={'depthToDay'} title={'Глубина-день'} />
|
|
||||||
<PrivateMenu.Link icon={<FolderOutlined />} key={'depthToInterval'} title={'Глубина-интервал'} />
|
|
||||||
<PrivateMenu.Link icon={<FolderOutlined />} key={'operationsSummary'} title={'Все операции'} />
|
|
||||||
<PrivateMenu.Link icon={<FolderOutlined />} key={'operationsToInterval'} title={'Операции-интервал'} />
|
|
||||||
</PrivateMenu>
|
|
||||||
|
|
||||||
<Layout>
|
|
||||||
<Content className={'site-layout-background'}>
|
|
||||||
<PrivateSwitch elseRedirect={['depthToDay', 'depthToInterval', 'operationsSummary', 'operationsToInterval']}>
|
|
||||||
<TelemetryAnalysisDepthToDay key={'depthToDay'} />
|
|
||||||
<TelemetryAnalysisDepthToInterval key={'depthToInterval'} />
|
|
||||||
<TelemetryAnalysisOperationsSummary key={'operationsSummary'} />
|
|
||||||
<TelemetryAnalysisOperationsToInterval key={'operationsToInterval'} />
|
|
||||||
</PrivateSwitch>
|
|
||||||
</Content>
|
|
||||||
</Layout>
|
|
||||||
</RootPathContext.Provider>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TelemetryAnalysis
|
|
Loading…
Reference in New Issue
Block a user