diff --git a/src/pages/WellOperations/Tvd.jsx b/src/pages/WellOperations/Tvd.jsx index 0f6f5e4..49279e4 100644 --- a/src/pages/WellOperations/Tvd.jsx +++ b/src/pages/WellOperations/Tvd.jsx @@ -1,5 +1,7 @@ -import { Switch } from 'antd' -import { memo, useState, useRef, useEffect } from 'react' +import { useHistory } from 'react-router-dom' +import { memo, useState, useRef, useEffect, useCallback } from 'react' +import { Switch, Button, InputNumber, Descriptions } from 'antd' +import { DoubleLeftOutlined, DoubleRightOutlined, FilterOutlined } from '@ant-design/icons' import { Chart, @@ -16,9 +18,27 @@ import ChartDataLabels from 'chartjs-plugin-datalabels' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' +import { makeDateColumn, makeNumericColumn, makeTextColumn, Table } from '@components/Table' +import { formatDate, fractionalSum } from '@utils/datetime' import { getOperations } from '@utils/functions' -Chart.register(TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin) +import '@styles/index.css' +import '@styles/tvd.less' + +const { Item } = Descriptions + +Chart.register( + TimeScale, + LinearScale, + LineController, + LineElement, + PointElement, + Legend, + ChartDataLabels, + zoomPlugin +) + +const numericRender = (value) => Number.isFinite(value) ? (+value).toFixed(2) : '-' const scaleTypes = { day: { @@ -51,10 +71,11 @@ const scaleTypes = { const defaultOptions = { responsive: true, - aspectRatio: 2.6, + maintainAspectRatio: false, + aspectRatio: false, interaction: { intersect: false, - mode: 'index', + mode: 'nearest', }, scales: { x: scaleTypes.day, @@ -89,14 +110,29 @@ const defaultOptions = { }, tooltip: { enabled: true, - callbacks: { - label: (tooltipItem) => tooltipItem.yLabel, - }, position: 'nearest', + callbacks: { + title: (items) => [ + `Дата: ${formatDate(items[0].raw?.date) ?? '-'}`, + `День с начала бурения: ${parseInt(items[0].raw?.day)}`, + ], + afterTitle: (items) => `Глубина: ${numericRender(items[0].raw?.depth)}`, + label: (item) => [ + item.raw.wellSectionTypeName + ': ' + item.raw.categoryName, + `Длительность (ч): ${item.raw.nptHours}` + ], + }, }, - } + }, } +const nptTableColumns = [ + makeTextColumn('Конструкция секции', 'wellSectionTypeName'), + makeNumericColumn('Глубина', 'depth'), + makeDateColumn('Дата начала', 'date'), + makeNumericColumn('Длительность (ч)', 'durationHours'), +] + const makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({ label, data, @@ -106,38 +142,101 @@ const makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({ borderDash, }) +const calcEndDate = (saubData) => { + if (!Array.isArray(saubData) || saubData.length <= 0) return [null, null] + const lastElm = saubData.at(-1) + return [saubData[0]?.date, fractionalSum(lastElm?.date, lastElm?.nptHours, 'hour')] +} + +const printDate = (date) => formatDate(date) ?? '-' + export const Tvd = memo(({ idWell, title }) => { - const [operations, setOperations] = useState([]) + const [operations, setOperations] = useState({}) const [isLoading, setIsLoading] = useState(false) + const [isTableLoading, setIsTableLoading] = useState(false) const [xLabel, setXLabel] = useState('day') const [chart, setChart] = useState() + const [npt, setNPT] = useState([]) + const [filteredNPT, setFilteredNPT] = useState([]) + const [filterValue, setFilterValue] = useState(null) + const [tableVisible, setTableVisible] = useState(true) + const [additionalData, setAdditionalData] = useState({}) const chartRef = useRef(null) + const history = useHistory() - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - const operations = await getOperations(idWell) - setOperations(operations) - }, - setIsLoading, - `Не удалось загрузить операции по скважине "${idWell}"`, - 'Получение списка опервций по скважине' - ) - }, [idWell]) + const onPointClick = useCallback((e) => { + const points = e?.chart?.tooltip?.dataPoints + if (!points || !(points.length > 0)) return + + const datasetId = points.find((p) => p.datasetIndex !== 1)?.datasetIndex + if (typeof datasetId === 'undefined') return + + const datasetName = datasetId === 2 ? 'plan' : 'fact' + const ids = points.filter((p) => p?.raw?.id).map((p) => p.raw.id) + const url = `/well/${idWell}/operations/${datasetName}/?selectedId=${ids.join(',')}` + history.push(url) + }, [idWell, history]) + + useEffect(() => invokeWebApiWrapperAsync( + async () => { + const operations = await getOperations(idWell) + setNPT(operations.fact.filter((row) => row.isNPT) ?? []) + setOperations(operations) + + const [factStartDate, factEndDate] = calcEndDate(operations.fact) + const [planStartDate, planEndDate] = calcEndDate(operations.plan) + const [predictStartDate, predictEndDate] = calcEndDate(operations.predict) + + const last = predictEndDate ?? factEndDate + setAdditionalData({ + lag: (+new Date(last) - +new Date(planEndDate)) / 86400_000, + endDate: last, + factStartDate, + factEndDate, + planStartDate, + planEndDate, + predictStartDate, + predictEndDate, + }) + }, + setIsLoading, + `Не удалось загрузить операции по скважине "${idWell}"`, + 'Получение списка опервций по скважине' + ), [idWell]) + + useEffect(() => invokeWebApiWrapperAsync( + async () => setFilteredNPT(npt.filter((row) => !filterValue || row.durationHours >= filterValue)), + setIsTableLoading, + 'Не удалось отфильтровать НПВ по времени' + ), [npt, filterValue]) useEffect(() => { + const npt = [] + operations?.fact?.forEach((row) => { + if (row?.isNPT !== false) return + const nptH = +(row.nptHours ?? 0) + npt.push({ + ...row, + day: row.day - nptH / 24, + date: fractionalSum(row.date, -nptH, 'hour'), + }) + }) + + const data = { datasets: [ - makeDataset(operations?.fact, 'Факт', '#0A0'), + makeDataset(operations?.fact, 'Факт', '#0A0', 3), makeDataset(operations?.predict, 'Прогноз', 'purple', 1, [7, 3]), - makeDataset(operations?.plan, 'План', '#C004', 4), + makeDataset(operations?.plan, 'План', '#F00', 3), + makeDataset(npt, 'Факт без НПВ', '#00F', 3) ] } if (chartRef.current && !chart) { const thisOptions = {} Object.assign(thisOptions, defaultOptions) + thisOptions.onClick = onPointClick thisOptions.scales.x = scaleTypes[xLabel] thisOptions.parsing.xAxisKey = xLabel @@ -155,27 +254,85 @@ export const Tvd = memo(({ idWell, title }) => { chart.options.scales.x = scaleTypes[xLabel] chart.options.parsing.xAxisKey = xLabel chart.update() + // Обнуление ширины необходимо для уменьшения размена при resize после появления элементов + chart.canvas.parentNode.style.width = '0' + chart.resize() } - }, [chart, operations, xLabel]) + }, [chart, operations, xLabel, onPointClick]) + + const toogleTable = useCallback(() => { + setOperations((pre) => ({ ...pre })) + setTableVisible((pre) => !pre) + }, []) return ( -