forked from ddrilling/asb_cloud_front
* Выделен стиль блока фильтрации
* Исправлена работа страница "Наработка"
This commit is contained in:
parent
39c1289d32
commit
7f2d337b4f
@ -11,6 +11,7 @@ import { makeColumn, makeDateColumn, makeNumericColumn, makeNumericSorter, makeT
|
|||||||
import { wrapPrivateComponent } from '@utils'
|
import { wrapPrivateComponent } from '@utils'
|
||||||
import { MessageService } from '@api'
|
import { MessageService } from '@api'
|
||||||
|
|
||||||
|
import '@styles/filter.less'
|
||||||
import '@styles/message.less'
|
import '@styles/message.less'
|
||||||
|
|
||||||
const pageSize = 26
|
const pageSize = 26
|
||||||
|
128
src/pages/Telemetry/OperationTime/index.jsx
Normal file
128
src/pages/Telemetry/OperationTime/index.jsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { memo, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Select } from 'antd'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import { useWell } from '@asb/context'
|
||||||
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import D3HorizontalPercentChart from '@components/d3/D3HorizontalPercentChart'
|
||||||
|
import { DateRangeWrapper, makeColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
||||||
|
import { arrayOrDefault, range, wrapPrivateComponent } from '@utils'
|
||||||
|
import { SubsystemOperationTimeService } from '@api'
|
||||||
|
|
||||||
|
import '@styles/filter.less'
|
||||||
|
import '@styles/operation_time.less'
|
||||||
|
|
||||||
|
const subsystemColors = [
|
||||||
|
'#1abc9c', '#16a085', '#2ecc71', '#27ae60',
|
||||||
|
'#3498db', '#2980b9', '#9b59b6', '#8e44ad',
|
||||||
|
'#34495e', '#2c3e50', '#f1c40f', '#f39c12',
|
||||||
|
'#e67e22', '#d35400', '#e74c3c', '#c0392b',
|
||||||
|
'#ecf0f1', '#bdc3c7', '#95a5a6', '#7f8c8d',
|
||||||
|
]
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
makeColumn('Цвет', 'color', { width: 50, render: (backgroundColor) => (
|
||||||
|
<div className={'table_color'} style={{ backgroundColor }} />
|
||||||
|
)}),
|
||||||
|
makeTextColumn('Подсистема', 'subsystemName'),
|
||||||
|
makeNumericColumn('Использование, %', 'kUsage', undefined, undefined, val => (+val * 100).toFixed(2)),
|
||||||
|
makeNumericColumn('Проходка, м', 'sumDepthInterval'),
|
||||||
|
makeNumericColumn('Время работы, ч', 'usedTimeHours'),
|
||||||
|
makeNumericColumn('Кол-во запусков', 'operationCount', undefined, undefined, makeNumericRender(0)),
|
||||||
|
]
|
||||||
|
|
||||||
|
// Выбор доступен только до текущей даты
|
||||||
|
const disabledDates = (current) => current && moment(current).isAfter(moment(), 'day', '[]')
|
||||||
|
// Выбор доступен только до текущего времени
|
||||||
|
const disabledTimes = (date) => ({
|
||||||
|
disabledHours: () => range(24).filter(h => date && moment(date).hours(h).isAfter(moment(), 'hour', '[]')),
|
||||||
|
disabledMinutes: () => range(60).filter(m => date && moment(date).minutes(m).isAfter(moment(), 'minute', '[]')),
|
||||||
|
disabledSeconds: () => range(60).filter(s => date && moment(date).seconds(s).isBetween(moment(), 'second', '[]'))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const OperationTime = memo(() => {
|
||||||
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [data, setData] = useState([])
|
||||||
|
const [selected, setSelected] = useState([])
|
||||||
|
const [dateRange, setDateRange] = useState([])
|
||||||
|
const [well] = useWell()
|
||||||
|
|
||||||
|
// Создаём массив пунктов для селектора подсистем
|
||||||
|
const typeOptions = useMemo(() => data.map((d) => ({ label: d.subsystemName, value: d.idSubsystem })), [data])
|
||||||
|
// Фильтруем данные по выбранным подсистемам
|
||||||
|
const selectedData = useMemo(() => data.filter((d) => selected.includes(d.idSubsystem)), [selected, data])
|
||||||
|
// Подготавливаем данные для отображения на графике
|
||||||
|
const chartData = useMemo(() => selectedData.map((d) => ({
|
||||||
|
name: d.subsystemName,
|
||||||
|
percent: d.kUsage * 100,
|
||||||
|
color: d.color,
|
||||||
|
})), [selectedData])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
if (!well.id) return
|
||||||
|
|
||||||
|
// Ограничение задаётся только если выбраны обе даты
|
||||||
|
const startDate = dateRange[1] ? dateRange[0]?.toISOString() : undefined
|
||||||
|
const endDate = dateRange[1]?.toISOString()
|
||||||
|
|
||||||
|
const data = await SubsystemOperationTimeService.getStat(well.id, undefined, startDate, endDate)
|
||||||
|
// Выбираем цвета для подсистем (если цветов не хватает начинаем сначала)
|
||||||
|
const coloredData = arrayOrDefault(data).map((d, i) => ({ ...d, color: subsystemColors[i % subsystemColors.length] }))
|
||||||
|
|
||||||
|
setData(coloredData)
|
||||||
|
setSelected(data.map((d) => d.idSubsystem)) // По-умолчанию выбираем все подсистемы
|
||||||
|
},
|
||||||
|
setShowLoader,
|
||||||
|
`Не удалось загрузить данные`,
|
||||||
|
{ actionName: 'Получение данных по скважине', well }
|
||||||
|
)
|
||||||
|
}, [dateRange, well])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoaderPortal show={showLoader}>
|
||||||
|
<div className={'filter-group'}>
|
||||||
|
<h3 className={'head'}>Фильтр подсистем</h3>
|
||||||
|
<div className={'body'}>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
mode={'multiple'}
|
||||||
|
options={typeOptions}
|
||||||
|
className={'filter-selector'}
|
||||||
|
onChange={setSelected}
|
||||||
|
value={selected}
|
||||||
|
/>
|
||||||
|
<DateRangeWrapper
|
||||||
|
onCalendarChange={setDateRange}
|
||||||
|
disabledDate={disabledDates}
|
||||||
|
disabledTime={disabledTimes}
|
||||||
|
value={dateRange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<D3HorizontalPercentChart
|
||||||
|
data={chartData}
|
||||||
|
colors={subsystemColors}
|
||||||
|
width={'100%'}
|
||||||
|
height={'50vh'}
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
bordered
|
||||||
|
size={'small'}
|
||||||
|
columns={tableColumns}
|
||||||
|
dataSource={selectedData}
|
||||||
|
scroll={{ y: '25vh', x: true }}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
</LoaderPortal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default wrapPrivateComponent(OperationTime, {
|
||||||
|
requirements: [], // SubsystemOperationTime.get
|
||||||
|
title: 'Наработка',
|
||||||
|
route: 'operation_time',
|
||||||
|
key: 'operation_time',
|
||||||
|
})
|
@ -1,153 +0,0 @@
|
|||||||
import React, { ReactNode, useEffect, useState } from 'react'
|
|
||||||
import { Col, Row, Select } from 'antd'
|
|
||||||
import { Moment } from 'moment'
|
|
||||||
|
|
||||||
import { DateRangeWrapper, makeColumn, makeNumericRender, Table } from '@components/Table'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
|
||||||
import D3HorizontalChart from '@components/d3/D3HorizontalChart'
|
|
||||||
import { useWell } from '@asb/context'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { SubsystemOperationTimeService } from '@api'
|
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
type DataType = {
|
|
||||||
idSubsystem: number
|
|
||||||
subsystemName: string
|
|
||||||
usedTimeHours: number
|
|
||||||
kUsage: number
|
|
||||||
sumDepthInterval: number
|
|
||||||
operationCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const subsystemColors = [
|
|
||||||
'#1abc9c',
|
|
||||||
'#16a085',
|
|
||||||
'#2ecc71',
|
|
||||||
'#27ae60',
|
|
||||||
'#3498db',
|
|
||||||
'#2980b9',
|
|
||||||
'#9b59b6',
|
|
||||||
'#8e44ad',
|
|
||||||
'#34495e',
|
|
||||||
'#2c3e50',
|
|
||||||
'#f1c40f',
|
|
||||||
'#f39c12',
|
|
||||||
'#e67e22',
|
|
||||||
'#d35400',
|
|
||||||
'#e74c3c',
|
|
||||||
'#c0392b',
|
|
||||||
'#ecf0f1',
|
|
||||||
'#bdc3c7',
|
|
||||||
'#95a5a6',
|
|
||||||
'#7f8c8d',
|
|
||||||
]
|
|
||||||
|
|
||||||
const tableColumns = [
|
|
||||||
makeColumn('Цвет', 'color', { width: 50, render: (color) => (
|
|
||||||
<div style={{ backgroundColor: color, padding: '5px 0' }} />
|
|
||||||
) }),
|
|
||||||
makeColumn('Подсистема', 'subsystemName'),
|
|
||||||
makeColumn('% использования', 'kUsage', { render: val => (+val * 100).toFixed(2) }),
|
|
||||||
makeColumn('Проходка, м', 'sumDepthInterval', {render: makeNumericRender(2)}),
|
|
||||||
makeColumn('Время работы, ч', 'usedTimeHours', {render: makeNumericRender(2)}),
|
|
||||||
makeColumn('Кол-во запусков', 'operationCount'),
|
|
||||||
]
|
|
||||||
|
|
||||||
const OperationTime = () => {
|
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
|
||||||
const [data, setData] = useState<DataType[]>([])
|
|
||||||
const [dateRange, setDateRange] = useState<Moment[]>([])
|
|
||||||
const [childrenData, setChildrenData] = useState<ReactNode[]>([])
|
|
||||||
const [well] = useWell()
|
|
||||||
|
|
||||||
const errorNotifyText = `Не удалось загрузить данные`
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
if (!well.id) return
|
|
||||||
try {
|
|
||||||
setData(arrayOrDefault(await SubsystemOperationTimeService.getStat(
|
|
||||||
well.id,
|
|
||||||
undefined,
|
|
||||||
dateRange[1] ? dateRange[0]?.toISOString() : undefined,
|
|
||||||
dateRange[1]?.toISOString(),
|
|
||||||
)))
|
|
||||||
} catch(e) {
|
|
||||||
setData([])
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setShowLoader,
|
|
||||||
errorNotifyText,
|
|
||||||
{ actionName: 'Получение данных по скважине', well }
|
|
||||||
)
|
|
||||||
}, [dateRange])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setChildrenData(data.map((item) => (
|
|
||||||
<Option key={item.subsystemName}>
|
|
||||||
{item.subsystemName}
|
|
||||||
</Option>
|
|
||||||
)))
|
|
||||||
}, [data])
|
|
||||||
|
|
||||||
const selectChange = (value: string[]) => {
|
|
||||||
|
|
||||||
setData(data.reduce((previousValue: DataType[], currentValue) => {
|
|
||||||
if (value.includes(currentValue.subsystemName)) {
|
|
||||||
previousValue.push(currentValue)
|
|
||||||
}
|
|
||||||
return previousValue
|
|
||||||
}, []))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoaderPortal show={showLoader}>
|
|
||||||
<h3 className={'filter-group-heading'}>Фильтр подсистем</h3>
|
|
||||||
<Row align={'middle'} justify={'space-between'} wrap={false} style={{ backgroundColor: 'white' }}>
|
|
||||||
<Col span={18}>
|
|
||||||
<Select
|
|
||||||
mode="multiple"
|
|
||||||
defaultValue={data.map(d => d.subsystemName)}
|
|
||||||
onChange={selectChange}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
{childrenData}
|
|
||||||
</Select>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<DateRangeWrapper
|
|
||||||
onCalendarChange={(dateRange: any) => setDateRange(dateRange)}
|
|
||||||
value={[dateRange[0], dateRange[1]]}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<div style={{width: '100%', height: '50vh'}}>
|
|
||||||
<D3HorizontalChart
|
|
||||||
data={data.map(item => ({name: item.subsystemName, percent: item.kUsage * 100}))}
|
|
||||||
colors={subsystemColors}
|
|
||||||
width={'100%'}
|
|
||||||
height={'50vh'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
size={'small'}
|
|
||||||
columns={tableColumns}
|
|
||||||
dataSource={data.map((d, i) => ({...d, color: subsystemColors[i]}))}
|
|
||||||
scroll={{ y: '25vh', x: true }}
|
|
||||||
pagination={false}
|
|
||||||
/>
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapPrivateComponent(OperationTime, {
|
|
||||||
requirements: [],
|
|
||||||
title: 'Наработка',
|
|
||||||
route: 'operation_time',
|
|
||||||
})
|
|
@ -116,9 +116,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-line line {
|
.grid-line:not(:first-of-type):not(:last-of-type) line {
|
||||||
stroke: #ddd;
|
stroke: #ddd;
|
||||||
stroke-dasharray: 4
|
stroke-dasharray: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1800px) {
|
@media (max-width: 1800px) {
|
||||||
|
22
src/styles/filter.less
Normal file
22
src/styles/filter.less
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.filter-group {
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
|
||||||
|
& .head {
|
||||||
|
margin: 5px auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .body {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
& .type-filter {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .filter-selector {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -39,29 +39,6 @@
|
|||||||
background: #505060;
|
background: #505060;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-group {
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
|
|
||||||
& .head {
|
|
||||||
margin: 5px auto;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .body {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
& .type-filter {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .filter-selector {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td.ant-table-column-sort {
|
td.ant-table-column-sort {
|
||||||
color: black;
|
color: black;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
|
3
src/styles/operation_time.less
Normal file
3
src/styles/operation_time.less
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.table_color {
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user