diff --git a/src/pages/WellOperations/Tvd.jsx b/src/pages/WellOperations/Tvd.jsx
deleted file mode 100644
index ae54cb8..0000000
--- a/src/pages/WellOperations/Tvd.jsx
+++ /dev/null
@@ -1,355 +0,0 @@
-import { useHistory } from 'react-router-dom'
-import { memo, useState, useRef, useEffect, useCallback } from 'react'
-import { Switch, Button, InputNumber, Descriptions } from 'antd'
-import { DoubleLeftOutlined, DoubleRightOutlined, ExportOutlined, FilterOutlined } from '@ant-design/icons'
-
-import {
- Chart,
- TimeScale,
- LinearScale,
- Legend,
- LineController,
- PointElement,
- LineElement
-} from 'chart.js'
-import 'chartjs-adapter-moment'
-import zoomPlugin from 'chartjs-plugin-zoom'
-import ChartDataLabels from 'chartjs-plugin-datalabels'
-
-import LoaderPortal from '@components/LoaderPortal'
-import { download, invokeWebApiWrapperAsync } from '@components/factory'
-import { makeDateColumn, makeNumericColumn, makeTextColumn, Table } from '@components/Table'
-import { formatDate, fractionalSum } from '@utils/datetime'
-import { getOperations } from '@utils/functions'
-
-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: {
- min: 0,
- type: 'linear',
- display: true,
- title: { display: false, text: '' },
- ticks: { stepSize: 1 }
- },
- date: {
- display: true,
- title: { display: true },
- type: 'time',
- time: {
- unit: 'hour',
- displayFormats: { 'hour': 'MM.DD' }
- },
- grid: { drawTicks: true },
- ticks: {
- stepSize: 3,
- major: { enabled: true },
- z: 1,
- display: true,
- textStrokeColor: '#fff',
- textStrokeWidth: 2,
- color: '#000',
- }
- }
-}
-
-const defaultOptions = {
- responsive: true,
- maintainAspectRatio: false,
- aspectRatio: false,
- interaction: {
- intersect: false,
- mode: 'nearest',
- },
- scales: {
- x: scaleTypes.day,
- y: {
- type: 'linear',
- position: 'top',
- reverse: true,
- display: true,
- title: {
- display: false,
- text: '',
- }
- }
- },
- parsing: {
- xAxisKey: 'day',
- yAxisKey: 'depth',
- },
- elements: {
- point: {
- radius: 1.7,
- //backgroundColor:'#aaa',
- //pointStyle:'triangle',
- },
- },
- plugins: {
- legend: {
- display: true,
- },
- datalabels: {
- display: false,
- },
- tooltip: {
- enabled: true,
- 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,
- backgroundColor: color,
- borderColor: color,
- borderWidth,
- 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 [isLoading, setIsLoading] = useState(false)
- const [isTableLoading, setIsTableLoading] = useState(false)
- const [isFileExporting, setIsFileExporting] = 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()
-
- 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', 3),
- makeDataset(operations?.predict, 'Прогноз', 'purple', 1, [7, 3]),
- 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
-
- const newChart = new Chart(chartRef.current, {
- type: 'line',
- plugins: [ChartDataLabels],
- options: thisOptions,
- data: data
- })
- setChart(newChart)
-
- return () => chart?.destroy()
- } else {
- chart.data = data
- 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, onPointClick])
-
- const toogleTable = useCallback(() => {
- setOperations((pre) => ({ ...pre }))
- setTableVisible((pre) => !pre)
- }, [])
-
- const onExport = useCallback(() => invokeWebApiWrapperAsync(
- async () => {
- await download(`/api/well/${idWell}/wellOperations/scheduleReport`)
- },
- setIsFileExporting,
- 'Не удалось загрузить файл'
- ), [idWell])
-
- return (
-
-
-
{title || 'График Глубина-день'}
-
- setXLabel(checked ? 'date' : 'day')}
- style={{ marginRight: '20px' }}
- />
- }
- loading={isFileExporting}
- onClick={onExport}
- style={{ marginRight: '5px' }}
- >Экспорт
- : }
- onClick={toogleTable}
- />
-
-
-
-
-
-
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}
- />
- ч.
-
-
-
-
-
- )}
-
-
-
- )
-})
-
-export default Tvd
diff --git a/src/pages/WellOperations/Tvd/AdditionalTables.jsx b/src/pages/WellOperations/Tvd/AdditionalTables.jsx
new file mode 100644
index 0000000..1889b63
--- /dev/null
+++ b/src/pages/WellOperations/Tvd/AdditionalTables.jsx
@@ -0,0 +1,67 @@
+import { Descriptions } from 'antd'
+import { memo, useEffect, useState } from 'react'
+
+import { makeNumericRender } from '@components/Table'
+import { invokeWebApiWrapperAsync } from '@components/factory'
+import { formatDate, fractionalSum } from '@utils/datetime'
+
+import '@styles/tvd.less'
+
+const { Item } = Descriptions
+
+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 numericRender = makeNumericRender(2)
+const printDate = (date) => formatDate(date) ?? '-'
+
+export const AdditionalTables = memo(({ operations, xLabel, setIsLoading }) => {
+ const [additionalData, setAdditionalData] = useState({})
+
+ useEffect(() => invokeWebApiWrapperAsync(
+ async () => {
+ 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,
+ 'Не удалось высчитать дополнительные данные'
+ ), [operations, setIsLoading])
+
+ return (
+ <>
+
+
+ - {printDate(additionalData.endDate)}
+ - {numericRender(additionalData.lag)}
+
+
+
+
+ - {numericRender(additionalData.lag)}
+ - {printDate(additionalData.planStartDate)}
+ - {printDate(additionalData.factStartDate)}
+ - {printDate(additionalData.planEndDate)}
+ - {printDate(additionalData.factEndDate)}
+
+
+ >
+ )
+})
+
+export default AdditionalTables
diff --git a/src/pages/WellOperations/Tvd/NetGraphExport.jsx b/src/pages/WellOperations/Tvd/NetGraphExport.jsx
new file mode 100644
index 0000000..72a5d72
--- /dev/null
+++ b/src/pages/WellOperations/Tvd/NetGraphExport.jsx
@@ -0,0 +1,29 @@
+import { memo, useCallback, useState } from 'react'
+import { ExportOutlined } from '@ant-design/icons'
+import { Button } from 'antd'
+
+import { download, invokeWebApiWrapperAsync } from '@components/factory'
+
+export const NetGraphExport = memo(({ idWell, ...other }) => {
+ const [isFileExporting, setIsFileExporting] = useState(false)
+
+ const onExport = useCallback(() => invokeWebApiWrapperAsync(
+ async () => await download(`/api/well/${idWell}/wellOperations/scheduleReport`),
+ setIsFileExporting,
+ 'Не удалось загрузить файл'
+ ), [idWell])
+
+ return (
+ }
+ loading={isFileExporting}
+ onClick={onExport}
+ style={{ marginRight: '5px' }}
+ {...other}
+ >
+ Сетевой график
+
+ )
+})
+
+export default NetGraphExport
diff --git a/src/pages/WellOperations/Tvd/NptTable.jsx b/src/pages/WellOperations/Tvd/NptTable.jsx
new file mode 100644
index 0000000..e6187e0
--- /dev/null
+++ b/src/pages/WellOperations/Tvd/NptTable.jsx
@@ -0,0 +1,66 @@
+import { memo, useEffect, useState } from 'react'
+import { InputNumber } from 'antd'
+import { FilterOutlined } from '@ant-design/icons'
+
+import LoaderPortal from '@components/LoaderPortal'
+import { invokeWebApiWrapperAsync } from '@components/factory'
+import { makeDateColumn, makeNumericColumn, makeTextColumn, Table } from '@components/Table'
+
+export const columns = [
+ makeTextColumn('Конструкция секции', 'wellSectionTypeName', null, null, null, { width: 140 }),
+ makeNumericColumn('Глубина', 'depth', null, null, null, 80),
+ makeDateColumn('Дата начала', 'date', false, undefined, { width: 90 }),
+ makeNumericColumn('Длительность (ч)', 'durationHours', null, null, null, 120),
+ makeTextColumn('Доп. инфо', 'categoryInfo', null, null, null),
+ makeTextColumn('Комментарий', 'comment'),
+]
+
+export const NptTable = memo(({ operations }) => {
+ const [filterValue, setFilterValue] = useState(0)
+ const [npt, setNPT] = useState([])
+ const [filteredNPT, setFilteredNPT] = useState([])
+ const [isTableLoading, setIsTableLoading] = useState(false)
+
+ useEffect(() => invokeWebApiWrapperAsync(
+ async () => setNPT(operations?.filter((row) => row?.isNPT) ?? []),
+ setIsTableLoading,
+ 'Не удалось получить список НПВ'
+ ), [operations])
+
+ useEffect(() => invokeWebApiWrapperAsync(
+ async () => setFilteredNPT(npt.filter((row) => !filterValue || row.durationHours >= filterValue)),
+ setIsTableLoading,
+ 'Не удалось отфильтровать НПВ по времени'
+ ), [npt, filterValue])
+
+ return (
+
+
+
+ Фильтр время НПВ ≥
+ setFilterValue(value ?? 0)}
+ value={filterValue}
+ />
+ ч.
+
+
+
+
+
+ )
+})
+
+export default NptTable
diff --git a/src/pages/WellOperations/Tvd/index.jsx b/src/pages/WellOperations/Tvd/index.jsx
new file mode 100644
index 0000000..e0e2784
--- /dev/null
+++ b/src/pages/WellOperations/Tvd/index.jsx
@@ -0,0 +1,226 @@
+import { useHistory } from 'react-router-dom'
+import { memo, useState, useRef, useEffect, useCallback } from 'react'
+import { DoubleLeftOutlined, DoubleRightOutlined } from '@ant-design/icons'
+import { Switch, Button } from 'antd'
+
+import {
+ Chart,
+ TimeScale,
+ LinearScale,
+ Legend,
+ LineController,
+ PointElement,
+ LineElement
+} from 'chart.js'
+import 'chartjs-adapter-moment'
+import zoomPlugin from 'chartjs-plugin-zoom'
+import ChartDataLabels from 'chartjs-plugin-datalabels'
+
+import LoaderPortal from '@components/LoaderPortal'
+import { invokeWebApiWrapperAsync } from '@components/factory'
+import { formatDate, fractionalSum } from '@utils/datetime'
+import { getOperations } from '@utils/functions'
+
+import NptTable from './NptTable'
+import NetGraphExport from './NetGraphExport'
+import AdditionalTables from './AdditionalTables'
+
+import '@styles/index.css'
+import '@styles/tvd.less'
+
+Chart.register(
+ TimeScale,
+ LinearScale,
+ LineController,
+ LineElement,
+ PointElement,
+ Legend,
+ ChartDataLabels,
+ zoomPlugin
+)
+
+const numericRender = (value) => Number.isFinite(value) ? (+value).toFixed(2) : '-'
+
+const scaleTypes = {
+ day: {
+ min: 0,
+ type: 'linear',
+ display: true,
+ title: { display: false, text: '' },
+ ticks: { stepSize: 1 }
+ },
+ date: {
+ display: true,
+ title: { display: true },
+ type: 'time',
+ time: { unit: 'day', displayFormats: { day: 'DD.MM.YYYY' } },
+ grid: { drawTicks: true },
+ ticks: {
+ stepSize: 3,
+ major: { enabled: true },
+ z: 1,
+ display: true,
+ textStrokeColor: '#fff',
+ textStrokeWidth: 2,
+ color: '#000',
+ }
+ }
+}
+
+const defaultOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ aspectRatio: false,
+ interaction: { intersect: false, mode: 'point' },
+ scales: {
+ x: scaleTypes.day,
+ y: {
+ type: 'linear',
+ position: 'top',
+ reverse: true,
+ display: true,
+ title: { display: false, text: '' }
+ }
+ },
+ parsing: { xAxisKey: 'day', yAxisKey: 'depth' },
+ elements: { point: { radius: 1.7 } },
+ plugins: {
+ legend: { display: true },
+ datalabels: { display: false },
+ tooltip: {
+ enabled: true,
+ 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 makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({
+ label,
+ data,
+ backgroundColor: color,
+ borderColor: color,
+ borderWidth,
+ borderDash,
+})
+
+export const Tvd = memo(({ idWell, title }) => {
+ const [chart, setChart] = useState()
+ const [xLabel, setXLabel] = useState('day')
+ const [operations, setOperations] = useState({})
+ const [tableVisible, setTableVisible] = useState(false)
+ const [isLoading, setIsLoading] = useState(false)
+
+ const chartRef = useRef(null)
+ const history = useHistory()
+
+ 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).join(',')
+ history.push(`/well/${idWell}/operations/${datasetName}/?selectedId=${ids}`)
+ }, [idWell, history])
+
+ useEffect(() => invokeWebApiWrapperAsync(
+ async () => setOperations(await getOperations(idWell)),
+ setIsLoading,
+ `Не удалось загрузить операции по скважине "${idWell}"`,
+ 'Получение списка опервций по скважине'
+ ), [idWell])
+
+ useEffect(() => {
+ const withoutNpt = []
+ operations?.fact?.forEach((row) => {
+ if (row?.isNPT !== false) return
+ const nptH = +(row.nptHours ?? 0)
+ withoutNpt.push({
+ ...row,
+ day: row.day - nptH / 24,
+ date: fractionalSum(row.date, -nptH, 'hour'),
+ })
+ })
+
+ const data = { datasets: [
+ makeDataset(operations?.fact, 'Факт', '#0A0', 3),
+ makeDataset(operations?.predict, 'Прогноз', 'purple', 1, [7, 3]),
+ makeDataset(operations?.plan, 'План', '#F00', 3),
+ makeDataset(withoutNpt, 'Факт без НПВ', '#00F', 3)
+ ]}
+
+ if (chartRef.current && !chart) {
+ const thisOptions = {}
+ Object.assign(thisOptions, defaultOptions)
+ thisOptions.onClick = onPointClick
+ thisOptions.scales.x = scaleTypes[xLabel]
+ thisOptions.parsing.xAxisKey = xLabel
+
+ const newChart = new Chart(chartRef.current, {
+ type: 'line',
+ plugins: [ChartDataLabels],
+ options: thisOptions,
+ data: data
+ })
+ setChart(newChart)
+
+ return () => chart?.destroy()
+ } else {
+ chart.data = data
+ 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, onPointClick])
+
+ const toogleTable = useCallback(() => {
+ setOperations(pre => ({ ...pre }))
+ setTableVisible(v => !v)
+ }, [])
+
+ return (
+
+
+
{title || 'График Глубина-день'}
+
+ setXLabel(checked ? 'date' : 'day')}
+ style={{ marginRight: '20px' }}
+ />
+
+ : } onClick={toogleTable}>НПВ
+
+
+
+
+
+
+ )
+})
+
+export default Tvd