From a55d0c9b42f2ee654f9e7cc6e0d52f4588ace32d Mon Sep 17 00:00:00 2001 From: goodm2ice Date: Thu, 17 Mar 2022 06:43:06 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D0=B0=20TVD=20=D0=B8=20=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B4=20getOperations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/WellOperations/Tvd.jsx | 237 +++++++++++++++++++++++++------ src/styles/tvd.less | 65 +++++++++ src/utils/functions.tsx | 31 ++-- 3 files changed, 282 insertions(+), 51 deletions(-) create mode 100644 src/styles/tvd.less 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 ( -
-
- {title || ( -

График Глубина-день

- )} - - -
- setXLabel(checked ? 'date' : 'day')} - /> -
-
+
+
+

{title || 'График Глубина-день'}

+
+ setXLabel(checked ? 'date' : 'day')} + style={{ marginRight: '20px' }} + /> +
+ +
+
+
console.log(e)}> + + {printDate(additionalData.endDate)} + {numericRender(additionalData.lag)} + +
+ +
+ + {numericRender(additionalData.lag)} + {printDate(additionalData.planStartDate)} + {printDate(additionalData.factStartDate)} + {printDate(additionalData.planEndDate)} + {printDate(additionalData.factEndDate)} + +
+
+ {tableVisible && ( +
+
+ + Фильтр время НПВ ≥ + setFilterValue(value ?? 0)} + value={filterValue} + /> + ч. +
+ + + + + )} + + ) }) diff --git a/src/styles/tvd.less b/src/styles/tvd.less new file mode 100644 index 0000000..52676ff --- /dev/null +++ b/src/styles/tvd.less @@ -0,0 +1,65 @@ +.tvd-page { + flex: 1; + display: flex; + flex-direction: column; + justify-content: stretch; + + .tvd-top { + display: flex; + align-items: baseline; + justify-content: space-between; + margin-top: 20px; + } + + .tvd-main { + height: 100%; + display: flex; + overflow: hidden; + align-items: stretch; + flex-flow: row nowrap; + + .tvd-left { + position: relative; + flex: 2; + + > div { + position: absolute; + //pointer-events: none; + transition: all .25s ease-out; + opacity: 1; + + &:hover { + opacity: 0; + } + + &.tvd-tr-table { + right: 15px; + top: 38px; + } + + &.tvd-bl-table { + bottom: 35px; + left: 50px; + } + } + + } + + .tvd-right { + flex: 1; + display: flex; + align-items: flex-end; + padding-left: 10px; + flex-direction: column; + justify-content: stretch; + + .tvd-npt-filter { + margin-bottom: 10px; + + span { + margin: 0 10px; + } + } + } + } +} diff --git a/src/utils/functions.tsx b/src/utils/functions.tsx index 453eb07..a6751d2 100644 --- a/src/utils/functions.tsx +++ b/src/utils/functions.tsx @@ -7,28 +7,37 @@ export const getPrecision = (number: number) => Number.isFinite(number) ? number export type KeyType = number | string -export type SaubData = { - key?: number - depth?: number - date?: string - day?: number +export const nwtIdCategory = 1043 + +export type SaubData = WellOperationDto & { + isNPT?: boolean // Относится ли операция к НПВ + key?: number // ID операции + depth?: number // Глубина + date?: string // Дата + nptHours?: number // Колличество часов НПВ с начала бурения до текущего момента } export const getOperations = async (idWell: number): Promise<{ - operations?: WellOperationDtoPlanFactPredictBase[], - plan?: SaubData[] - fact?: SaubData[] - predict?: SaubData[] + operations: WellOperationDtoPlanFactPredictBase[], + plan: SaubData[] + fact: SaubData[] + predict: SaubData[] }> => { const ops = await OperationStatService.getTvd(idWell) - if (!ops) return {} + if (!ops) return { + operations: [], + plan: [], + fact: [], + predict: [], + } const convert = (operation?: WellOperationDto): SaubData => ({ + ...operation, key: operation?.id, depth: operation?.depthStart, date: operation?.dateStart, - day: operation?.day, + isNPT: operation?.idCategory === nwtIdCategory, }) const planData = ops