2022-03-13 22:11:58 +05:00
|
|
|
|
import { Table as RawTable, Typography } from 'antd'
|
|
|
|
|
import { Fragment, memo, useCallback, useEffect, useState } from 'react'
|
|
|
|
|
|
|
|
|
|
import LoaderPortal from '@components/LoaderPortal'
|
|
|
|
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
2022-03-23 14:28:08 +05:00
|
|
|
|
import { WellSelector } from '@components/selectors/WellSelector'
|
2022-03-17 06:42:43 +05:00
|
|
|
|
import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
2022-03-13 22:11:58 +05:00
|
|
|
|
import { OperationStatService, WellOperationService } from '@api'
|
|
|
|
|
import { arrayOrDefault } from '@utils'
|
|
|
|
|
|
|
|
|
|
import '@styles/index.css'
|
|
|
|
|
import '@styles/statistics.less'
|
|
|
|
|
|
|
|
|
|
const { Text } = Typography
|
|
|
|
|
const { Summary } = RawTable
|
|
|
|
|
const { Cell, Row } = Summary
|
|
|
|
|
|
|
|
|
|
const numericRender = makeNumericRender()
|
|
|
|
|
const speedNumericRender = (section) => numericRender(section?.speed)
|
|
|
|
|
|
2022-03-14 10:48:36 +05:00
|
|
|
|
const makeSectionSorter = (key, name) => (a, b) => (a?.[key]?.[name] ?? 0) - (b?.[key]?.[name] ?? 0)
|
2022-03-13 22:11:58 +05:00
|
|
|
|
export const makeSectionColumn = (title, key, { speedRender } = {}) => makeGroupColumn(title, [
|
2022-03-14 10:48:36 +05:00
|
|
|
|
makeNumericColumn('Проходка', key, null, null, (section => numericRender(section?.depth)), 100, {
|
|
|
|
|
sorter: makeSectionSorter(key, 'depth'),
|
|
|
|
|
}),
|
|
|
|
|
makeNumericColumn('Время', key, null, null, (section => numericRender(section?.time)), 100, {
|
|
|
|
|
sorter: makeSectionSorter(key, 'time'),
|
|
|
|
|
}),
|
|
|
|
|
makeNumericColumn((<>V<sub>рейсовая</sub></>), key, null, null, speedRender ?? speedNumericRender, 100, {
|
|
|
|
|
sorter: makeSectionSorter(key, 'speed'),
|
|
|
|
|
}),
|
2022-03-13 22:11:58 +05:00
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
export const defaultColumns = [
|
|
|
|
|
//makeTextColumn('Куст', 'cluster', null, null, null, { fixed: 'left', width: 100 }),
|
|
|
|
|
makeTextColumn('Скважина', 'caption', null, null, null, { fixed: 'left', width: 100 }),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const scrollSettings = { scrollToFirstRowOnChange: true, x: 100, y: 200 }
|
|
|
|
|
const summaryColSpan = 1 /// TODO: Когда добавится куст изменить на 2
|
|
|
|
|
|
|
|
|
|
const getWellData = async (wellsList) => {
|
|
|
|
|
const stats = arrayOrDefault(await OperationStatService.getWellsStat(wellsList))
|
|
|
|
|
const wellData = stats.map((well) => {
|
|
|
|
|
const stat = {
|
|
|
|
|
// cluster: null,
|
|
|
|
|
caption: well.caption,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
well.sections?.forEach(({ id, fact }) => {
|
|
|
|
|
if (!fact) return
|
|
|
|
|
stat[`section_${id}`] = {
|
|
|
|
|
time: (+new Date(fact.end) - +new Date(fact.start)) / 3600_000,
|
|
|
|
|
depth: fact.wellDepthEnd - fact.wellDepthStart,
|
|
|
|
|
speed: fact.routeSpeed,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return stat
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return wellData
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const Statistics = memo(({ idWell }) => {
|
|
|
|
|
const [sectionTypes, setSectionTypes] = useState([])
|
|
|
|
|
const [avgColumns, setAvgColumns] = useState(defaultColumns)
|
|
|
|
|
const [cmpColumns, setCmpColumns] = useState(defaultColumns)
|
|
|
|
|
const [isAvgTableLoading, setIsAvgTableLoading] = useState(false)
|
|
|
|
|
const [isCmpTableLoading, setIsCmpTableLoading] = useState(false)
|
|
|
|
|
const [isPageLoading, setIsPageLoading] = useState(false)
|
|
|
|
|
const [avgWells, setAvgWells] = useState([])
|
|
|
|
|
const [cmpWells, setCmpWells] = useState([])
|
|
|
|
|
const [avgData, setAvgData] = useState([])
|
|
|
|
|
const [cmpData, setCmpData] = useState([])
|
|
|
|
|
const [avgRow, setAvgRow] = useState({})
|
|
|
|
|
|
|
|
|
|
const cmpSpeedRender = useCallback((key) => (section) => {
|
|
|
|
|
let spanClass = ''
|
|
|
|
|
// Дополнительная проверка на "null" необходима, чтобы значение "0" не стало исключением
|
|
|
|
|
if ((avgRow[key]?.speed ?? null) !== null && (section?.speed ?? null) !== null) {
|
|
|
|
|
const avgSpeed = avgRow[key].speed - section.speed
|
|
|
|
|
if (avgSpeed < 0)
|
|
|
|
|
spanClass = 'high-efficienty'
|
|
|
|
|
else if (avgSpeed > 0)
|
|
|
|
|
spanClass = 'low-efficienty'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span className={spanClass}>
|
|
|
|
|
{numericRender(section?.speed)}
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
}, [avgRow])
|
|
|
|
|
|
|
|
|
|
useEffect(() => invokeWebApiWrapperAsync(
|
|
|
|
|
async () => {
|
|
|
|
|
const types = await WellOperationService.getSectionTypes(idWell)
|
|
|
|
|
setSectionTypes(Object.entries(types))
|
2022-03-14 10:35:47 +05:00
|
|
|
|
},
|
|
|
|
|
setIsPageLoading,
|
|
|
|
|
`Не удалось получить типы секции`,
|
|
|
|
|
`Получение списка возможных секций`,
|
|
|
|
|
), [idWell])
|
|
|
|
|
|
|
|
|
|
useEffect(() => invokeWebApiWrapperAsync(
|
|
|
|
|
async () => {
|
|
|
|
|
const filteredSections = avgData?.length > 0 ? sectionTypes.filter(([id, _]) => avgData.some((row) => `section_${id}` in row)) : sectionTypes
|
|
|
|
|
|
2022-03-13 22:11:58 +05:00
|
|
|
|
setAvgColumns([
|
|
|
|
|
...defaultColumns,
|
2022-03-14 10:35:47 +05:00
|
|
|
|
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
2022-03-13 22:11:58 +05:00
|
|
|
|
])
|
|
|
|
|
setCmpColumns([
|
|
|
|
|
...defaultColumns,
|
2022-03-14 10:35:47 +05:00
|
|
|
|
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
2022-03-13 22:11:58 +05:00
|
|
|
|
speedRender: cmpSpeedRender(`section_${id}`)
|
|
|
|
|
}))
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
setIsPageLoading,
|
2022-03-14 10:35:47 +05:00
|
|
|
|
'Не удалось установить необходимые столбцы'
|
|
|
|
|
), [sectionTypes, avgData, cmpSpeedRender])
|
2022-03-13 22:11:58 +05:00
|
|
|
|
|
|
|
|
|
useEffect(() => invokeWebApiWrapperAsync(
|
|
|
|
|
async () => {
|
|
|
|
|
const avgData = await getWellData(avgWells)
|
|
|
|
|
setAvgData(avgData)
|
|
|
|
|
|
|
|
|
|
const avgRow = {}
|
|
|
|
|
|
|
|
|
|
avgData.forEach((row) => row && Object.keys(row).forEach((key) => {
|
|
|
|
|
if (!key.startsWith('section_')) return
|
|
|
|
|
if (!avgRow[key]) avgRow[key] = { depth: 0, time: 0, speed: 0, count: 0 }
|
|
|
|
|
avgRow[key].depth += row[key].depth ?? 0
|
|
|
|
|
avgRow[key].time += row[key].time ?? 0
|
|
|
|
|
avgRow[key].speed += row[key].speed ?? 0
|
|
|
|
|
avgRow[key].count++
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
Object.values(avgRow).forEach((section) => section.speed /= section.count)
|
|
|
|
|
|
|
|
|
|
setAvgRow(avgRow)
|
|
|
|
|
},
|
|
|
|
|
setIsAvgTableLoading,
|
|
|
|
|
'Не удалось загрузить данные для расчёта средних значений',
|
|
|
|
|
), [avgWells])
|
|
|
|
|
|
|
|
|
|
useEffect(() => invokeWebApiWrapperAsync(
|
|
|
|
|
async () => {
|
|
|
|
|
const cmpData = await getWellData(cmpWells)
|
|
|
|
|
setCmpData(cmpData)
|
|
|
|
|
},
|
|
|
|
|
setIsCmpTableLoading,
|
|
|
|
|
'Не удалось получить скважины для сравнения',
|
|
|
|
|
), [cmpWells])
|
|
|
|
|
|
|
|
|
|
const getStatisticsAvgSummary = useCallback((data) => (
|
|
|
|
|
<Summary fixed={'bottom'}>
|
|
|
|
|
<Row>
|
|
|
|
|
<Cell index={0} colSpan={summaryColSpan}>
|
|
|
|
|
<Text>Итого:</Text>
|
|
|
|
|
</Cell>
|
|
|
|
|
{sectionTypes.map(([id, _], i) => (
|
|
|
|
|
<Fragment key={id ?? i}>
|
|
|
|
|
<Cell index={3 * i + summaryColSpan}>
|
|
|
|
|
<Text>{numericRender(avgRow[`section_${id}`]?.depth)}</Text>
|
|
|
|
|
</Cell>
|
|
|
|
|
<Cell index={3 * i + summaryColSpan + 1}>
|
|
|
|
|
<Text>{numericRender(avgRow[`section_${id}`]?.time)}</Text>
|
|
|
|
|
</Cell>
|
|
|
|
|
<Cell index={3 * i + summaryColSpan + 2}>
|
|
|
|
|
<Text>{numericRender(avgRow[`section_${id}`]?.speed)}</Text>
|
|
|
|
|
</Cell>
|
|
|
|
|
</Fragment>
|
|
|
|
|
))}
|
|
|
|
|
</Row>
|
|
|
|
|
</Summary>
|
|
|
|
|
), [avgRow, sectionTypes])
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={'statistics-page'}>
|
|
|
|
|
<LoaderPortal show={isPageLoading}>
|
|
|
|
|
<h2 style={{ padding: '10px' }}>Расчёт средних значений без Цифровой буровой</h2>
|
|
|
|
|
<div className={'average-table'}>
|
|
|
|
|
<div className={'well-selector'}>
|
|
|
|
|
<span className={'well-selector-label'}>Выберите скважины для расчёта средних значений:</span>
|
|
|
|
|
<WellSelector
|
|
|
|
|
idWell={idWell}
|
|
|
|
|
value={avgWells}
|
|
|
|
|
style={{ flex: 100 }}
|
|
|
|
|
onChange={setAvgWells}
|
|
|
|
|
placeholder={'Ничего не выбрано'}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<Table
|
|
|
|
|
bordered
|
|
|
|
|
size={'small'}
|
|
|
|
|
pagination={false}
|
|
|
|
|
columns={avgColumns}
|
|
|
|
|
dataSource={avgData}
|
|
|
|
|
scroll={scrollSettings}
|
|
|
|
|
loading={isAvgTableLoading}
|
|
|
|
|
summary={getStatisticsAvgSummary}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={'compare-table'}>
|
|
|
|
|
<div className={'well-selector'}>
|
|
|
|
|
<span className={'well-selector-label'}>Выберите скважины сравнения:</span>
|
|
|
|
|
<WellSelector
|
|
|
|
|
idWell={idWell}
|
|
|
|
|
value={cmpWells}
|
|
|
|
|
style={{ flex: 100 }}
|
|
|
|
|
onChange={setCmpWells}
|
|
|
|
|
placeholder={'Ничего не выбрано'}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<Table
|
|
|
|
|
bordered
|
|
|
|
|
size={'small'}
|
|
|
|
|
pagination={false}
|
|
|
|
|
columns={cmpColumns}
|
|
|
|
|
dataSource={cmpData}
|
|
|
|
|
scroll={scrollSettings}
|
|
|
|
|
loading={isCmpTableLoading}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</LoaderPortal>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export default Statistics
|