asb_cloud_front/src/pages/Analytics/Statistics.jsx

234 lines
9.6 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'
import { WellSelector } from '@components/selectors/WellSelector'
import { 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