forked from ddrilling/asb_cloud_front
234 lines
9.6 KiB
JavaScript
234 lines
9.6 KiB
JavaScript
import { Table as RawTable, Typography } from 'antd'
|
||
import { Fragment, memo, useCallback, useEffect, useState } from 'react'
|
||
|
||
import LoaderPortal from '@components/LoaderPortal'
|
||
import { WellSelector } from '@components/WellSelector'
|
||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||
import { makeColumn, makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
||
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)
|
||
|
||
const makeSectionSorter = (key, name) => (a, b) => (a?.[key]?.[name] ?? 0) - (b?.[key]?.[name] ?? 0)
|
||
export const makeSectionColumn = (title, key, { speedRender } = {}) => makeGroupColumn(title, [
|
||
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'),
|
||
}),
|
||
])
|
||
|
||
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))
|
||
},
|
||
setIsPageLoading,
|
||
`Не удалось получить типы секции`,
|
||
`Получение списка возможных секций`,
|
||
), [idWell])
|
||
|
||
useEffect(() => invokeWebApiWrapperAsync(
|
||
async () => {
|
||
const filteredSections = avgData?.length > 0 ? sectionTypes.filter(([id, _]) => avgData.some((row) => `section_${id}` in row)) : sectionTypes
|
||
|
||
setAvgColumns([
|
||
...defaultColumns,
|
||
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
||
])
|
||
setCmpColumns([
|
||
...defaultColumns,
|
||
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
||
speedRender: cmpSpeedRender(`section_${id}`)
|
||
}))
|
||
])
|
||
},
|
||
setIsPageLoading,
|
||
'Не удалось установить необходимые столбцы'
|
||
), [sectionTypes, avgData, cmpSpeedRender])
|
||
|
||
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
|